/*-------------------------------------------------------------------------
 *
 * nodeWindowAgg.c
 *	  处理WindowAgg节点的例程。
 *
 * WindowAgg节点在输入元组集的适当分区中评估"窗口函数"。任何一个WindowAgg只能用于单个窗口规范，尽管它可以评估多个共享相同窗口规范的窗口函数。输入元组必须以排序顺序提供，PARTITION BY列（如果有）作为主要排序键，ORDER BY列（如果有）作为次要排序键。
 * （如果查询涉及多个窗口规范，规划器将根据需要生成一系列WindowAgg和介入的Sort节点。）
 *
 * 由于窗口函数可能需要访问当前分区中的所有行，因此我们将分区中的行累积到tuplestore中。窗口函数使用WindowObject API调用，以便按需访问这些行。
 *
 * 我们还支持将普通聚合函数用作窗口函数。对于这些函数，为每个分区模拟常规Agg节点环境。根据SQL规范，输出表示当前行的窗口帧中所有行的聚合函数的值。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/backend/executor/nodeWindowAgg.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/htup_details.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_proc.h"
#include "executor/executor.h"
#include "executor/nodeWindowAgg.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/optimizer.h"
#include "parser/parse_agg.h"
#include "parser/parse_coerce.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/datum.h"
#include "utils/expandeddatum.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/regproc.h"
#include "utils/syscache.h"
#include "windowapi.h"

/*
 * 所有窗口函数 API 都是通过这个对象调用的，该对象作为 fcinfo->context 传递给窗口函数。
 */
typedef struct WindowObjectData
{
	NodeTag		type;
	WindowAggState *winstate;	/* parent WindowAggState */
	List	   *argstates;		/* 函数参数的 ExprState 树 */
	void	   *localmem;		/* WinGetPartitionLocalMemory 的块 */
	int			markptr;		/* 此函数的 tuplestore 标记指针 */
	int			readptr;		/* 此函数的 tuplestore 读取指针 */
	int64		markpos;		/* 标记指针定位的行 */
	int64		seekpos;		/* 读取指针定位的行 */
} WindowObjectData;

/*
 * 对于这个节点处理的每个窗口函数和窗口聚合，我们都有一个 WindowStatePerFunc 结构。
 */
typedef struct WindowStatePerFuncData
{
	/* 链接到此工作状态的 WindowFunc 表达式和状态节点 */
	WindowFuncExprState *wfuncstate;
	WindowFunc *wfunc;

	int			numArguments;	/* number of arguments */

	FmgrInfo	flinfo;			/* 窗口函数的 fmgr 查找数据 */

	Oid			winCollation;	/* 从窗口函数推导的排序规则 */

	/*
	 * 我们需要每个函数结果的 len 和 byval 信息，以便知道如何复制/删除值。
	 */
	int16		resulttypeLen;
	bool		resulttypeByVal;

	bool		plain_agg;		/* 它只是一个普通的聚合函数吗？ */
	int			aggno;			/* 如果是，则其 WindowStatePerAggData 的索引 */

	WindowObject winobj;		/* 在窗口函数 API 中使用的对象 */
}			WindowStatePerFuncData;

/*
 * 对于普通的聚合窗口函数，我们也有一个这样的对象。
 */
typedef struct WindowStatePerAggData
{
	/* 过渡函数的 Oids */
	Oid			transfn_oid;
	Oid			invtransfn_oid; /* 可能是 InvalidOid */
	Oid			finalfn_oid;	/* 可能是 InvalidOid */

	/*
	 * 过渡函数的 fmgr 查找数据 --- 仅在相应的 oid 不是 InvalidOid 时有效。
	 * 特别要注意 fn_strict 标志在这里保留。
	 */
	FmgrInfo	transfn;
	FmgrInfo	invtransfn;
	FmgrInfo	finalfn;

	int			numFinalArgs;	/* 传递给 finalfn 的参数数量 */

	/*
	 * 来自 pg_aggregate 条目的初始值
	 */
	Datum		initValue;
	bool		initValueIsNull;

	/*
	 * 当前帧边界的缓存值
	 */
	Datum		resultValue;
	bool		resultValueIsNull;

	/*
	 * 我们需要聚合输入、结果和过渡数据类型的 len 和 byval 信息，以便知道如何复制/删除值。
	 */
	int16		inputtypeLen,
				resulttypeLen,
				transtypeLen;
	bool		inputtypeByVal,
				resulttypeByVal,
				transtypeByVal;

	int			wfuncno;		/* 关联的 WindowStatePerFuncData 的索引 */

	/* 包含过渡值和可能的其他辅助数据的上下文 */
	MemoryContext aggcontext;	/* 可能是私有的，或 winstate->aggcontext */

	/* 当前过渡值 */
	Datum		transValue;		/* 当前过渡值 */
	bool		transValueIsNull;

	int64		transValueCount;	/* 当前聚合的行数 */

	/* 与 eval_windowaggregates() 本地的数据 */
	bool		restart;		/* 需要在这个循环中重新启动这个聚合吗？ */
} WindowStatePerAggData;

static void fc_initialize_windowaggregate(WindowAggState *fc_winstate,
									   WindowStatePerFunc fc_perfuncstate,
									   WindowStatePerAgg fc_peraggstate);
static void fc_advance_windowaggregate(WindowAggState *fc_winstate,
									WindowStatePerFunc fc_perfuncstate,
									WindowStatePerAgg fc_peraggstate);
static bool fc_advance_windowaggregate_base(WindowAggState *fc_winstate,
										 WindowStatePerFunc fc_perfuncstate,
										 WindowStatePerAgg fc_peraggstate);
static void fc_finalize_windowaggregate(WindowAggState *fc_winstate,
									 WindowStatePerFunc fc_perfuncstate,
									 WindowStatePerAgg fc_peraggstate,
									 Datum *fc_result, bool *fc_isnull);

static void fc_eval_windowaggregates(WindowAggState *fc_winstate);
static void fc_eval_windowfunction(WindowAggState *fc_winstate,
								WindowStatePerFunc fc_perfuncstate,
								Datum *fc_result, bool *fc_isnull);

static void fc_begin_partition(WindowAggState *fc_winstate);
static void fc_spool_tuples(WindowAggState *fc_winstate, int64 fc_pos);
static void fc_release_partition(WindowAggState *fc_winstate);

static int	fc_row_is_in_frame(WindowAggState *fc_winstate, int64 fc_pos,
							TupleTableSlot *fc_slot);
static void update_frameheadpos(WindowAggState *fc_winstate);
static void update_frametailpos(WindowAggState *fc_winstate);
static void update_grouptailpos(WindowAggState *fc_winstate);

static WindowStatePerAggData *fc_initialize_peragg(WindowAggState *fc_winstate,
												WindowFunc *fc_wfunc,
												WindowStatePerAgg fc_peraggstate);
static Datum fc_GetAggInitVal(Datum fc_textInitVal, Oid fc_transtype);

static bool fc_are_peers(WindowAggState *fc_winstate, TupleTableSlot *fc_slot1,
					  TupleTableSlot *fc_slot2);
static bool fc_window_gettupleslot(WindowObject fc_winobj, int64 fc_pos,
								TupleTableSlot *fc_slot);


/*
 * initialize_windowaggregate
 * 与 nodeAgg.c 中的 initialize_aggregates 并行
 */
static void fc_initialize_windowaggregate(WindowAggState *fc_winstate,
						   WindowStatePerFunc fc_perfuncstate,
						   WindowStatePerAgg fc_peraggstate)
{
	MemoryContext fc_oldContext;

	/*
	 * 如果我们使用私有聚合上下文，我们可以在这里重置它。但如果上下文是共享的，我们不知道其他聚合是否仍然需要它，因此我们必须留给调用者在适当的时间重置。
	 */
	if (fc_peraggstate->aggcontext != fc_winstate->aggcontext)
		MemoryContextResetAndDeleteChildren(fc_peraggstate->aggcontext);

	if (fc_peraggstate->initValueIsNull)
		fc_peraggstate->transValue = fc_peraggstate->initValue;
	else
	{
		fc_oldContext = MemoryContextSwitchTo(fc_peraggstate->aggcontext);
		fc_peraggstate->transValue = datumCopy(fc_peraggstate->initValue,
											fc_peraggstate->transtypeByVal,
											fc_peraggstate->transtypeLen);
		MemoryContextSwitchTo(fc_oldContext);
	}
	fc_peraggstate->transValueIsNull = fc_peraggstate->initValueIsNull;
	fc_peraggstate->transValueCount = 0;
	fc_peraggstate->resultValue = (Datum) 0;
	fc_peraggstate->resultValueIsNull = true;
}

/*
 * advance_windowaggregate
 * 与 nodeAgg.c 中的 advance_aggregates 并行
 */
static void fc_advance_windowaggregate(WindowAggState *fc_winstate,
						WindowStatePerFunc fc_perfuncstate,
						WindowStatePerAgg fc_peraggstate)
{
	LOCAL_FCINFO(fcinfo, FUNC_MAX_ARGS);
	WindowFuncExprState *fc_wfuncstate = fc_perfuncstate->wfuncstate;
	int			fc_numArguments = fc_perfuncstate->numArguments;
	Datum		fc_newVal;
	ListCell   *fc_arg;
	int			fc_i;
	MemoryContext fc_oldContext;
	ExprContext *fc_econtext = fc_winstate->tmpcontext;
	ExprState  *fc_filter = fc_wfuncstate->aggfilter;

	fc_oldContext = MemoryContextSwitchTo(fc_econtext->ecxt_per_tuple_memory);

	/* 跳过任何被过滤掉的内容 */
	if (fc_filter)
	{
		bool		fc_isnull;
		Datum		fc_res = ExecEvalExpr(fc_filter, fc_econtext, &fc_isnull);

		if (fc_isnull || !DatumGetBool(fc_res))
		{
			MemoryContextSwitchTo(fc_oldContext);
			return;
		}
	}

	/* 我们从 1 开始，因为第 0 个参数将是过渡值 */
	fc_i = 1;
	foreach(fc_arg, fc_wfuncstate->args)
	{
		ExprState  *fc_argstate = (ExprState *) lfirst(fc_arg);

		fcinfo->args[fc_i].value = ExecEvalExpr(fc_argstate, fc_econtext,
											 &fcinfo->args[fc_i].isnull);
		fc_i++;
	}

	if (fc_peraggstate->transfn.fn_strict)
	{
		/*
		 * 对于严格的 transfn，当输入为 NULL 时，不会发生任何事情；我们只是保留先前的 transValue。注意 transValueCount 也不会变化。
		 */
		for (fc_i = 1; fc_i <= fc_numArguments; fc_i++)
		{
			if (fcinfo->args[fc_i].isnull)
			{
				MemoryContextSwitchTo(fc_oldContext);
				return;
			}
		}

		/*
		 * 对于初始值为 NULL 的严格过渡函数，我们使用第一个非 NULL 输入作为初始状态。（我们已经检查了聚合的输入类型与其 transtype 是二进制兼容的，因此此处的直接复制是可以的。）
		 *
		 * 如果是按引用传递的数据类型，我们必须将数据拷贝到 aggcontext 中。我们不需要释放旧的 transValue，因为它是 NULL。
		 */
		if (fc_peraggstate->transValueCount == 0 && fc_peraggstate->transValueIsNull)
		{
			MemoryContextSwitchTo(fc_peraggstate->aggcontext);
			fc_peraggstate->transValue = datumCopy(fcinfo->args[1].value,
												fc_peraggstate->transtypeByVal,
												fc_peraggstate->transtypeLen);
			fc_peraggstate->transValueIsNull = false;
			fc_peraggstate->transValueCount = 1;
			MemoryContextSwitchTo(fc_oldContext);
			return;
		}

		if (fc_peraggstate->transValueIsNull)
		{
			/*
			 * 不要用 NULL 输入调用严格函数。请注意，尽管进行了上述测试，但如果 transfn 是严格的 *并且* 在先前循环中返回了 NULL，则可能会到达这里。如果发生这种情况，我们将传播 NULL 直到最后。不过，这只能发生在没有逆过渡函数的情况下，因为如果存在逆过渡函数，我们会禁止过渡回 NULL。
			 */
			MemoryContextSwitchTo(fc_oldContext);
			Assert(!OidIsValid(fc_peraggstate->invtransfn_oid));
			return;
		}
	}

	/*
	 * 调用过渡函数是可以的。在调用时设置 winstate->curaggcontext，以便可能被 AggCheckCallContext 使用。
	 */
	InitFunctionCallInfoData(*fcinfo, &(fc_peraggstate->transfn),
							 fc_numArguments + 1,
							 fc_perfuncstate->winCollation,
							 (void *) fc_winstate, NULL);
	fcinfo->args[0].value = fc_peraggstate->transValue;
	fcinfo->args[0].isnull = fc_peraggstate->transValueIsNull;
	fc_winstate->curaggcontext = fc_peraggstate->aggcontext;
	fc_newVal = FunctionCallInvoke(fcinfo);
	fc_winstate->curaggcontext = NULL;

	/*
	 * 移动聚合过渡函数必须不返回 null，见 advance_windowaggregate_base()。
	 */
	if (fcinfo->isnull && OidIsValid(fc_peraggstate->invtransfn_oid))
		ereport(ERROR,
				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
				 errmsg("moving-aggregate transition function must not return null")));

	/*
	 * 我们必须跟踪包含在 transValue 中的行数，因为为了删除最后一个输入，advance_windowaggregate_base() 必须不调用逆过渡函数，而是简单地将 transValue 重置回其初始值。
	 */
	fc_peraggstate->transValueCount++;

	/*
	 * 如果是按引用传递的数据类型，必须将新值复制到 aggcontext 中并释放先前的 transValue。但如果 transfn 返回指向其第一个输入的指针，我们不需要做任何事情。此外，如果 transfn 返回指向已是 aggcontext 子对象的 R/W 扩展对象的指针，则可以假设我们可以在不复制的情况下采用该值。
	 */
	if (!fc_peraggstate->transtypeByVal &&
		DatumGetPointer(fc_newVal) != DatumGetPointer(fc_peraggstate->transValue))
	{
		if (!fcinfo->isnull)
		{
			MemoryContextSwitchTo(fc_peraggstate->aggcontext);
			if (DatumIsReadWriteExpandedObject(fc_newVal,
											   false,
											   fc_peraggstate->transtypeLen) &&
				MemoryContextGetParent(DatumGetEOHP(fc_newVal)->eoh_context) == CurrentMemoryContext)
				 /* 不执行任何操作 */ ;
			else
				fc_newVal = datumCopy(fc_newVal,
								   fc_peraggstate->transtypeByVal,
								   fc_peraggstate->transtypeLen);
		}
		if (!fc_peraggstate->transValueIsNull)
		{
			if (DatumIsReadWriteExpandedObject(fc_peraggstate->transValue,
											   false,
											   fc_peraggstate->transtypeLen))
				DeleteExpandedObject(fc_peraggstate->transValue);
			else
				pfree(DatumGetPointer(fc_peraggstate->transValue));
		}
	}

	MemoryContextSwitchTo(fc_oldContext);
	fc_peraggstate->transValue = fc_newVal;
	fc_peraggstate->transValueIsNull = fcinfo->isnull;
}

/*
 * advance_windowaggregate_base
 * 从聚合中移除最旧的元组。
 *
 * 这与 advance_windowaggregate 非常相似，除了我们将调用逆过渡函数（由调用者检查是否可用）。
 *
 * 如果我们成功从这个聚合中移除当前行，则返回 true，否则返回 false（在后者情况下，调用者负责通过重新启动聚合进行清理）。
 */
static bool fc_advance_windowaggregate_base(WindowAggState *fc_winstate,
							 WindowStatePerFunc fc_perfuncstate,
							 WindowStatePerAgg fc_peraggstate)
{
	LOCAL_FCINFO(fcinfo, FUNC_MAX_ARGS);
	WindowFuncExprState *fc_wfuncstate = fc_perfuncstate->wfuncstate;
	int			fc_numArguments = fc_perfuncstate->numArguments;
	Datum		fc_newVal;
	ListCell   *fc_arg;
	int			fc_i;
	MemoryContext fc_oldContext;
	ExprContext *fc_econtext = fc_winstate->tmpcontext;
	ExprState  *fc_filter = fc_wfuncstate->aggfilter;

	fc_oldContext = MemoryContextSwitchTo(fc_econtext->ecxt_per_tuple_memory);

	/* 跳过任何被过滤掉的内容 */
	if (fc_filter)
	{
		bool		fc_isnull;
		Datum		fc_res = ExecEvalExpr(fc_filter, fc_econtext, &fc_isnull);

		if (fc_isnull || !DatumGetBool(fc_res))
		{
			MemoryContextSwitchTo(fc_oldContext);
			return true;
		}
	}

	/* 我们从 1 开始，因为第 0 个参数将是过渡值 */
	fc_i = 1;
	foreach(fc_arg, fc_wfuncstate->args)
	{
		ExprState  *fc_argstate = (ExprState *) lfirst(fc_arg);

		fcinfo->args[fc_i].value = ExecEvalExpr(fc_argstate, fc_econtext,
											 &fcinfo->args[fc_i].isnull);
		fc_i++;
	}

	if (fc_peraggstate->invtransfn.fn_strict)
	{
		/*
		 * 对于严格的 (inv)transfn，当输入为 NULL 时，不会发生任何事情；我们只是保留先前的 transValue。注意 transValueCount 也不会变化。
		 */
		for (fc_i = 1; fc_i <= fc_numArguments; fc_i++)
		{
			if (fcinfo->args[fc_i].isnull)
			{
				MemoryContextSwitchTo(fc_oldContext);
				return true;
			}
		}
	}

	/* 应该仍然有一个已添加但尚未移除的值 */
	Assert(fc_peraggstate->transValueCount > 0);

	/*
	 * 在移动聚合模式下，状态必须永远不为 NULL，除非在聚合任何行之前（此时肯定不是这种情况）。这一限制使我们能够将来自逆函数的 NULL 结果理解为“抱歉，无法在这种情况下进行逆过渡”。我们已经在 advance_windowaggregate 中检查过这一点，但为了安全起见，再次检查。
	 */
	if (fc_peraggstate->transValueIsNull)
		elog(ERROR, "aggregate transition value is NULL before inverse transition");

	/*
	 * 我们不能使用逆过渡函数来移除最后一个输入。这样做会产生一个非 NULL 状态，而我们应该在此之后处于初始状态，这可能很可能是 NULL。因此，在这种情况下，我们只是重新初始化聚合。
	 */
	if (fc_peraggstate->transValueCount == 1)
	{
		MemoryContextSwitchTo(fc_oldContext);
		fc_initialize_windowaggregate(fc_winstate,
								   &fc_winstate->perfunc[fc_peraggstate->wfuncno],
								   fc_peraggstate);
		return true;
	}

	/*
	 * 调用逆过渡函数是可以的。在调用时设置 winstate->curaggcontext，以便可能被 AggCheckCallContext 使用。
	 */
	InitFunctionCallInfoData(*fcinfo, &(fc_peraggstate->invtransfn),
							 fc_numArguments + 1,
							 fc_perfuncstate->winCollation,
							 (void *) fc_winstate, NULL);
	fcinfo->args[0].value = fc_peraggstate->transValue;
	fcinfo->args[0].isnull = fc_peraggstate->transValueIsNull;
	fc_winstate->curaggcontext = fc_peraggstate->aggcontext;
	fc_newVal = FunctionCallInvoke(fcinfo);
	fc_winstate->curaggcontext = NULL;

	/*
	 * 如果函数返回 NULL，则报告失败，强制重新启动。
	 */
	if (fcinfo->isnull)
	{
		MemoryContextSwitchTo(fc_oldContext);
		return false;
	}

	/* 更新包含在 transValue 中的行数 */
	fc_peraggstate->transValueCount--;

	
	if (!fc_peraggstate->transtypeByVal &&
		DatumGetPointer(fc_newVal) != DatumGetPointer(fc_peraggstate->transValue))
	{
		if (!fcinfo->isnull)
		{
			MemoryContextSwitchTo(fc_peraggstate->aggcontext);
			if (DatumIsReadWriteExpandedObject(fc_newVal,
											   false,
											   fc_peraggstate->transtypeLen) &&
				MemoryContextGetParent(DatumGetEOHP(fc_newVal)->eoh_context) == CurrentMemoryContext)
				 /* 不执行任何操作 */ ;
			else
				fc_newVal = datumCopy(fc_newVal,
								   fc_peraggstate->transtypeByVal,
								   fc_peraggstate->transtypeLen);
		}
		if (!fc_peraggstate->transValueIsNull)
		{
			if (DatumIsReadWriteExpandedObject(fc_peraggstate->transValue,
											   false,
											   fc_peraggstate->transtypeLen))
				DeleteExpandedObject(fc_peraggstate->transValue);
			else
				pfree(DatumGetPointer(fc_peraggstate->transValue));
		}
	}

	MemoryContextSwitchTo(fc_oldContext);
	fc_peraggstate->transValue = fc_newVal;
	fc_peraggstate->transValueIsNull = fcinfo->isnull;

	return true;
}

/*
 * finalize_windowaggregate
 * parallel to finalize_aggregate in nodeAgg.c
 */
static void fc_finalize_windowaggregate(WindowAggState *fc_winstate,
						 WindowStatePerFunc fc_perfuncstate,
						 WindowStatePerAgg fc_peraggstate,
						 Datum *fc_result, bool *fc_isnull)
{
	MemoryContext fc_oldContext;

	fc_oldContext = MemoryContextSwitchTo(fc_winstate->ss.ps.ps_ExprContext->ecxt_per_tuple_memory);

	/*
	 * 如果提供了agg的finalfn，则应用它，否则返回transValue。
	 */
	if (OidIsValid(fc_peraggstate->finalfn_oid))
	{
		LOCAL_FCINFO(fcinfo, FUNC_MAX_ARGS);
		int			fc_numFinalArgs = fc_peraggstate->numFinalArgs;
		bool		fc_anynull;
		int			fc_i;

		InitFunctionCallInfoData(fcinfodata.fcinfo, &(fc_peraggstate->finalfn),
								 fc_numFinalArgs,
								 fc_perfuncstate->winCollation,
								 (void *) fc_winstate, NULL);
		fcinfo->args[0].value =
			MakeExpandedObjectReadOnly(fc_peraggstate->transValue,
									   fc_peraggstate->transValueIsNull,
									   fc_peraggstate->transtypeLen);
		fcinfo->args[0].isnull = fc_peraggstate->transValueIsNull;
		fc_anynull = fc_peraggstate->transValueIsNull;

		/* 用null填充任何剩余的参数位置 */
		for (fc_i = 1; fc_i < fc_numFinalArgs; fc_i++)
		{
			fcinfo->args[fc_i].value = (Datum) 0;
			fcinfo->args[fc_i].isnull = true;
			fc_anynull = true;
		}

		if (fcinfo->flinfo->fn_strict && fc_anynull)
		{
			/* 不要用NULL输入调用严格函数 */
			*fc_result = (Datum) 0;
			*fc_isnull = true;
		}
		else
		{
			fc_winstate->curaggcontext = fc_peraggstate->aggcontext;
			*fc_result = FunctionCallInvoke(fcinfo);
			fc_winstate->curaggcontext = NULL;
			*fc_isnull = fcinfo->isnull;
		}
	}
	else
	{
		/* 不需要MakeExpandedObjectReadOnly；datumCopy将复制它 */
		*fc_result = fc_peraggstate->transValue;
		*fc_isnull = fc_peraggstate->transValueIsNull;
	}

	/*
	 * 如果结果是按引用传递的，确保它位于正确的上下文中。
	 */
	if (!fc_peraggstate->resulttypeByVal && !*fc_isnull &&
		!MemoryContextContains(CurrentMemoryContext,
							   DatumGetPointer(*fc_result)))
		*fc_result = datumCopy(*fc_result,
							fc_peraggstate->resulttypeByVal,
							fc_peraggstate->resulttypeLen);
	MemoryContextSwitchTo(fc_oldContext);
}

/*
 * eval_windowaggregates
 * 评估作为窗口函数使用的简单聚合
 *
 * 这与 nodeAgg.c 有两个不同之处。首先，如果窗口的帧
 * 开始位置移动，我们使用逆转换函数（如果存在）来从
 * 转换值中移除行。其次，我们希望在将更多数据聚合到
 * 相同的转换值后能重复调用聚合的最终函数。这不是 nodeAgg.c
 * 所要求的行为。
 */
static void fc_eval_windowaggregates(WindowAggState *fc_winstate)
{
	WindowStatePerAgg fc_peraggstate;
	int			fc_wfuncno,
				fc_numaggs,
				fc_numaggs_restart,
				fc_i;
	int64		fc_aggregatedupto_nonrestarted;
	MemoryContext fc_oldContext;
	ExprContext *fc_econtext;
	WindowObject fc_agg_winobj;
	TupleTableSlot *fc_agg_row_slot;
	TupleTableSlot *fc_temp_slot;

	fc_numaggs = fc_winstate->numaggs;
	if (fc_numaggs == 0)
		return;					/* 无需操作 */

	/* 最终输出执行在 ps_ExprContext 中 */
	fc_econtext = fc_winstate->ss.ps.ps_ExprContext;
	fc_agg_winobj = fc_winstate->agg_winobj;
	fc_agg_row_slot = fc_winstate->agg_row_slot;
	fc_temp_slot = fc_winstate->temp_slot_1;

	/*
	 * 如果窗口的帧开始子句是 UNBOUNDED_PRECEDING 且未指定
	 * 排除子句，则窗口帧由从分区起点向前延伸的连续一组行
	 * 组成，当前行向前推进时，行只会进入帧，而不会退出。
	 * 这使得使用增量策略评估聚合成为可能：我们对每个添加
	 * 到帧的行运行转换函数，并在每当需要当前聚合值时
	 * 运行最终函数。这比对每个当前行重新运行整个聚合计算
	 * 的幼稚方法要高效得多。它假设最终函数不会损坏当前的
	 * 转换值，但在 nodeAgg.c 中我们也有相同的假设（当
	 * 它重新扫描现有的哈希表时）。
	 *
	 * 如果帧开始有时会移动，我们仍然可以在连续的行共享
	 * 相同的帧头时如上进行优化，不过如果帧头移动超出了
	 * 先前的头，我们将尝试使用聚合的逆转换函数来移除这些
	 * 行。此函数将聚合的当前状态恢复到如果未聚合被移除行时
	 * 的状态。逆转换函数可以选择返回 NULL，表示函数无法
	 * 从聚合中移除元组。如果发生这种情况，或者聚合根本没有
	 * 逆转换函数，则我们必须在新的帧边界内对所有元组
	 * 重新进行聚合。
	 *
	 * 如果有任何排除子句，则我们可能需要在一组不连续的
	 * 行上进行聚合，因此我们不得不每行重新计算。
	 * （对于某些帧结束选择，帧可能总是连续的，但这是一个
	 * 之后可以调查的优化。）
	 *
	 * 在许多常见情况下，多行共享相同的帧，因此共享
	 * 相同的聚合值。（特别是，如果在 RANGE 窗口中没有
	 * ORDER BY，则所有行都是同级，因此它们的窗口帧等于
	 * 整个分区。）我们通过在达到同级组的第一行时一次性
	 * 计算聚合值，然后为所有后续行返回保存的值来优化
	 * 此类情况。
	 *
	 * 'aggregatedupto' 跟踪尚未累积到聚合转换值中的
	 * 第一行。每当我们开始一个新的同级组时，
	 * 我们向前累积到同级组的末尾。
	 */

	/*
	 * 首先，更新帧头位置。
	 *
	 * 帧头应该永远不能向后移动，如果向后移动，下面的代码
	 * 将无法处理，所以出于安全起见，如果它移动了，我们会
	 * 抱怨。
	 */
	update_frameheadpos(fc_winstate);
	if (fc_winstate->frameheadpos < fc_winstate->aggregatedbase)
		elog(ERROR, "window frame head moved backward");

	/*
	 * 如果与前一行相比帧没有变化，我们可以重用
	 * 之前保存在此函数底部的结果值。由于我们尚不知道
	 * 当前帧的结束，因此无法完全检查。但如果帧结束模式
	 * 是 UNBOUNDED FOLLOWING 或 CURRENT ROW，且未指定
	 * 排除子句，并且当前行位于前一行的帧内，则两个帧的
	 * 结束必须重合。注意，在聚合的第一行时，aggregatedbase ==
	 * aggregatedupto，这意味着此测试必须失败，因此我们
	 * 不需要在此处显式检查“没有前一行”的情况。
	 */
	if (fc_winstate->aggregatedbase == fc_winstate->frameheadpos &&
		(fc_winstate->frameOptions & (FRAMEOPTION_END_UNBOUNDED_FOLLOWING |
								   FRAMEOPTION_END_CURRENT_ROW)) &&
		!(fc_winstate->frameOptions & FRAMEOPTION_EXCLUSION) &&
		fc_winstate->aggregatedbase <= fc_winstate->currentpos &&
		fc_winstate->aggregatedupto > fc_winstate->currentpos)
	{
		for (fc_i = 0; fc_i < fc_numaggs; fc_i++)
		{
			fc_peraggstate = &fc_winstate->peragg[fc_i];
			fc_wfuncno = fc_peraggstate->wfuncno;
			fc_econtext->ecxt_aggvalues[fc_wfuncno] = fc_peraggstate->resultValue;
			fc_econtext->ecxt_aggnulls[fc_wfuncno] = fc_peraggstate->resultValueIsNull;
		}
		return;
	}

	/*----------
	 * 初始化重新开始标志。
	 *
	 * 我们重新开始聚合：
	 *	 - 如果我们正在处理分区中的第一行，或
	 *	 - 如果帧的头移动且我们无法使用逆转换
	 *	   函数，或
	 *	 - 我们有一个排除子句，或
	 *	 - 如果新帧与旧帧不重叠
	 *
	 * 请注意，在最后一种情况下我们并不严格需要重新开始，
	 * 但如果我们无论如何要将所有行从聚合中移除，重新开始
	 * 肯定会更快。
	 *----------
	 */
	fc_numaggs_restart = 0;
	for (fc_i = 0; fc_i < fc_numaggs; fc_i++)
	{
		fc_peraggstate = &fc_winstate->peragg[fc_i];
		if (fc_winstate->currentpos == 0 ||
			(fc_winstate->aggregatedbase != fc_winstate->frameheadpos &&
			 !OidIsValid(fc_peraggstate->invtransfn_oid)) ||
			(fc_winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
			fc_winstate->aggregatedupto <= fc_winstate->frameheadpos)
		{
			fc_peraggstate->restart = true;
			fc_numaggs_restart++;
		}
		else
			fc_peraggstate->restart = false;
	}

	/*
	 * 如果我们有任何可能移动的聚合，尝试将 aggregatedbase
	 * 向前推进以匹配帧的头，通过从聚合中移除脱落的输入行。
	 * 这可能会失败，即 advance_windowaggregate_base() 可能返回
	 * false，在这种情况下，我们将在下面重新开始该聚合。
	 */
	while (fc_numaggs_restart < fc_numaggs &&
		   fc_winstate->aggregatedbase < fc_winstate->frameheadpos)
	{
		
/*
		 * 获取要删除的下一个元组。这应该永远不会失败，
		 * 因为我们之前应该已经在这里。
		 */
		if (!fc_window_gettupleslot(fc_agg_winobj, fc_winstate->aggregatedbase,
								 fc_temp_slot))
			elog(ERROR, "could not re-fetch previously fetched frame row");

		/* 设置元组上下文以评估聚合参数 */
		fc_winstate->tmpcontext->ecxt_outertuple = fc_temp_slot;

		/*
		 * 对窗口中的每个聚合函数执行逆转移，除非它已经被标记为需要重新启动。
		 */
		for (fc_i = 0; fc_i < fc_numaggs; fc_i++)
		{
			bool		fc_ok;

			fc_peraggstate = &fc_winstate->peragg[fc_i];
			if (fc_peraggstate->restart)
				continue;

			fc_wfuncno = fc_peraggstate->wfuncno;
			fc_ok = fc_advance_windowaggregate_base(fc_winstate,
											  &fc_winstate->perfunc[fc_wfuncno],
											  fc_peraggstate);
			if (!fc_ok)
			{
				/* 逆转移函数失败，必须重新启动 */
				fc_peraggstate->restart = true;
				fc_numaggs_restart++;
			}
		}

		/* 在每个元组后重置每个输入元组上下文 */
		ResetExprContext(fc_winstate->tmpcontext);

		/* 并推进聚合行状态 */
		fc_winstate->aggregatedbase++;
		ExecClearTuple(fc_temp_slot);
	}

	/*
	 * 如果我们成功推进了所有聚合的基础行，
	 * aggregatedbase 现在等于 frameheadpos；但如果我们在任何一个上失败了，必须强制更新 aggregatedbase。
	 */
	fc_winstate->aggregatedbase = fc_winstate->frameheadpos;

	/*
	 * 如果我们为聚合创建了标记指针，保持其向上推至帧头，
	 * 以便 tuplestore 可以丢弃不必要的行。
	 */
	if (fc_agg_winobj->markptr >= 0)
		WinSetMarkPosition(fc_agg_winobj, fc_winstate->frameheadpos);

	/*
	 * 现在重新启动需要它的聚合。
	 *
	 * 我们假设使用共享上下文的聚合如果 *任何* 聚合重新启动，
	 * 都会始终重新启动，因此如果是这种情况，我们可以清理
	 * 共享 aggcontext。私有 aggcontexts 如果其拥有的聚合重新启动，
	 * 则通过 initialize_windowaggregate() 重置。如果我们
	 * 没有重新启动聚合，我们需要释放之前保存的
	 * 结果，否则会造成内存泄漏。
	 */
	if (fc_numaggs_restart > 0)
		MemoryContextResetAndDeleteChildren(fc_winstate->aggcontext);
	for (fc_i = 0; fc_i < fc_numaggs; fc_i++)
	{
		fc_peraggstate = &fc_winstate->peragg[fc_i];

		/* 使用共享上下文的聚合如果 *任何* 聚合重新启动，必须重新启动 */
		Assert(fc_peraggstate->aggcontext != fc_winstate->aggcontext ||
			   fc_numaggs_restart == 0 ||
			   fc_peraggstate->restart);

		if (fc_peraggstate->restart)
		{
			fc_wfuncno = fc_peraggstate->wfuncno;
			fc_initialize_windowaggregate(fc_winstate,
									   &fc_winstate->perfunc[fc_wfuncno],
									   fc_peraggstate);
		}
		else if (!fc_peraggstate->resultValueIsNull)
		{
			if (!fc_peraggstate->resulttypeByVal)
				pfree(DatumGetPointer(fc_peraggstate->resultValue));
			fc_peraggstate->resultValue = (Datum) 0;
			fc_peraggstate->resultValueIsNull = true;
		}
	}

	/*
	 * 未重新启动的聚合现在包含 aggregatedbase
	 * （即 frameheadpos）和 aggregatedupto 之间的行，而重新启动的聚合
	 * 不包含任何行。如果有任何重新启动的聚合，因此我们必须
	 * 从 frameheadpos 重新开始聚合，否则我们可以简单地
	 * 在 aggregatedupto 处继续。我们必须记住
	 * aggregatedupto 的旧值，以知道跳过未重新启动聚合的推进长度。
	 * 如果我们修改了 aggregatedupto，必须根据下面的循环不变量清除
	 * agg_row_slot。
	 */
	fc_aggregatedupto_nonrestarted = fc_winstate->aggregatedupto;
	if (fc_numaggs_restart > 0 &&
		fc_winstate->aggregatedupto != fc_winstate->frameheadpos)
	{
		fc_winstate->aggregatedupto = fc_winstate->frameheadpos;
		ExecClearTuple(fc_agg_row_slot);
	}

	/*
	 * 继续前进，直到我们到达不在帧中的行（或分区的结束）。
	 *
	 * 注意循环不变量：agg_row_slot 要么为空，要么包含
	 * 在位置 aggregatedupto 的行。我们在处理
	 * 一行后推进 aggregatedupto。
	 */
	for (;;)
	{
		int			fc_ret;

		/* 如果我们还没有，获取下一行 */
		if (TupIsNull(fc_agg_row_slot))
		{
			if (!fc_window_gettupleslot(fc_agg_winobj, fc_winstate->aggregatedupto,
									 fc_agg_row_slot))
				break;			/* 必须是分区的结束 */
		}

		/*
		 * 如果不能再有更多行在帧中，则退出循环。如果
		 * 当前行不在帧中但帧中可能还有更多，则跳过聚合。
		 */
		fc_ret = fc_row_is_in_frame(fc_winstate, fc_winstate->aggregatedupto, fc_agg_row_slot);
		if (fc_ret < 0)
			break;
		if (fc_ret == 0)
			goto next_tuple;

		/* 设置元组上下文以评估聚合参数 */
		fc_winstate->tmpcontext->ecxt_outertuple = fc_agg_row_slot;

		/* 将行积累到聚合中 */
		for (fc_i = 0; fc_i < fc_numaggs; fc_i++)
		{
			fc_peraggstate = &fc_winstate->peragg[fc_i];

			/* 未重新启动的聚合跳过直到 aggregatedupto_nonrestarted */
			if (!fc_peraggstate->restart &&
				fc_winstate->aggregatedupto < fc_aggregatedupto_nonrestarted)
				continue;

			fc_wfuncno = fc_peraggstate->wfuncno;
			fc_advance_windowaggregate(fc_winstate,
									&fc_winstate->perfunc[fc_wfuncno],
									fc_peraggstate);
		}

next_tuple:
		/* 在每个元组后重置每个输入元组上下文 */
		ResetExprContext(fc_winstate->tmpcontext);

		/* 并推进聚合行状态 */
		fc_winstate->aggregatedupto++;
		ExecClearTuple(fc_agg_row_slot);
	}

	/* 帧的结束不应该向后移动，永远不应该 */
	Assert(fc_aggregatedupto_nonrestarted <= fc_winstate->aggregatedupto);

	/*
	 * 最终聚合并填充结果/是空字段。
	 */
	for (fc_i = 0; fc_i < fc_numaggs; fc_i++)
	{
		Datum	   *fc_result;
		bool	   *fc_isnull;

		fc_peraggstate = &fc_winstate->peragg[fc_i];
		fc_wfuncno = fc_peraggstate->wfuncno;
		fc_result = &fc_econtext->ecxt_aggvalues[fc_wfuncno];
		fc_isnull = &fc_econtext->ecxt_aggnulls[fc_wfuncno];
		fc_finalize_windowaggregate(fc_winstate,
								 &fc_winstate->perfunc[fc_wfuncno],
								 fc_peraggstate,
								 fc_result, fc_isnull);

		/*
		 * 保存结果以防下一个行共享相同的帧。
		 *
		 * XXX 在某些框架模式中，例如 ROWS/END_CURRENT_ROW， 
		 * 我们可以预先知道下一个行不可能共享相同的帧。值得
		 * 检测这一点并跳过此代码吗？
		 */
		if (!fc_peraggstate->resulttypeByVal && !*fc_isnull)
		{
			fc_oldContext = MemoryContextSwitchTo(fc_peraggstate->aggcontext);
			fc_peraggstate->resultValue =
				datumCopy(*fc_result,
						  fc_peraggstate->resulttypeByVal,
						  fc_peraggstate->resulttypeLen);
			MemoryContextSwitchTo(fc_oldContext);
		}
		else
		{
			fc_peraggstate->resultValue = *fc_result;
		}
		fc_peraggstate->resultValueIsNull = *fc_isnull;
	}
}

/*
 * eval_windowfunction
 *
 * 窗口函数的参数在这里不被评估，因为窗口
 * 函数可能需要随机访问分区中的任意行。
 * 窗口函数使用特殊的 WinGetFuncArgInPartition 和
 * WinGetFuncArgInFrame 函数来评估它想要的行的参数。
 */
static void fc_eval_windowfunction(WindowAggState *fc_winstate, WindowStatePerFunc fc_perfuncstate,
					Datum *fc_result, bool *fc_isnull)
{
	LOCAL_FCINFO(fcinfo, FUNC_MAX_ARGS);
	MemoryContext fc_oldContext;

	fc_oldContext = MemoryContextSwitchTo(fc_winstate->ss.ps.ps_ExprContext->ecxt_per_tuple_memory);

	/*
	 * 我们不向窗口函数传递任何普通参数，但我们确实传递
	 * 参数的数量，以允许窗口函数的实现支持不同数量的参数。
	 * 真实的信息通过 WindowObject 传递，后者通过 fcinfo->context 传递。
	 */
	InitFunctionCallInfoData(*fcinfo, &(fc_perfuncstate->flinfo),
							 fc_perfuncstate->numArguments,
							 fc_perfuncstate->winCollation,
							 (void *) fc_perfuncstate->winobj, NULL);
	/* 以防万一，使所有常规参数槽为 null */
	for (int fc_argno = 0; fc_argno < fc_perfuncstate->numArguments; fc_argno++)
		fcinfo->args[fc_argno].isnull = true;
	/* 窗口函数没有当前的聚合上下文，亦是如此 */
	fc_winstate->curaggcontext = NULL;

	*fc_result = FunctionCallInvoke(fcinfo);
	*fc_isnull = fcinfo->isnull;

	/*
	 * 确保传递引用的数据在适当的上下文中分配。 (我们需要这条，因为函数可能返回指向某个短生命元组的指针，这是完全可能的。)
	 */
	if (!fc_perfuncstate->resulttypeByVal && !fcinfo->isnull &&
		!MemoryContextContains(CurrentMemoryContext,
							   DatumGetPointer(*fc_result)))
		*fc_result = datumCopy(*fc_result,
							fc_perfuncstate->resulttypeByVal,
							fc_perfuncstate->resulttypeLen);

	MemoryContextSwitchTo(fc_oldContext);
}

/*
 * begin_partition
 * 开始缓冲下一分区的行。
 */
static void fc_begin_partition(WindowAggState *fc_winstate)
{
	WindowAgg  *fc_node = (WindowAgg *) fc_winstate->ss.ps.plan;
	PlanState  *fc_outerPlan = outerPlanState(fc_winstate);
	int			fc_frameOptions = fc_winstate->frameOptions;
	int			fc_numfuncs = fc_winstate->numfuncs;
	int			fc_i;

	fc_winstate->partition_spooled = false;
	fc_winstate->framehead_valid = false;
	fc_winstate->frametail_valid = false;
	fc_winstate->grouptail_valid = false;
	fc_winstate->spooled_rows = 0;
	fc_winstate->currentpos = 0;
	fc_winstate->frameheadpos = 0;
	fc_winstate->frametailpos = 0;
	fc_winstate->currentgroup = 0;
	fc_winstate->frameheadgroup = 0;
	fc_winstate->frametailgroup = 0;
	fc_winstate->groupheadpos = 0;
	fc_winstate->grouptailpos = -1;	/* 请参见 update_grouptailpos */
	ExecClearTuple(fc_winstate->agg_row_slot);
	if (fc_winstate->framehead_slot)
		ExecClearTuple(fc_winstate->framehead_slot);
	if (fc_winstate->frametail_slot)
		ExecClearTuple(fc_winstate->frametail_slot);

	/*
	 * 如果这是第一个分区，我们需要获取第一输入行以存储在 first_part_slot 中。
	 */
	if (TupIsNull(fc_winstate->first_part_slot))
	{
		TupleTableSlot *fc_outerslot = ExecProcNode(fc_outerPlan);

		if (!TupIsNull(fc_outerslot))
			ExecCopySlot(fc_winstate->first_part_slot, fc_outerslot);
		else
		{
			/* 外部计划为空，因此我们没有任何操作 */
			fc_winstate->partition_spooled = true;
			fc_winstate->more_partitions = false;
			return;
		}
	}

	/* 为该分区创建新的元组存储 */
	fc_winstate->buffer = tuplestore_begin_heap(false, false, work_mem);

	/*
	 * 为元组存储设置读取指针。当前指针不需要向后能力，但每个窗口函数的读取指针需要，并且如果我们可能需要重新启动聚合，聚合指针也需要。
	 */
	fc_winstate->current_ptr = 0;	/* 读取指针 0 是预先分配的 */

	/* 为当前指针重置默认的倒带能力位 */
	tuplestore_set_eflags(fc_winstate->buffer, 0);

	/* 如果需要，为聚合创建读取指针 */
	if (fc_winstate->numaggs > 0)
	{
		WindowObject fc_agg_winobj = fc_winstate->agg_winobj;
		int			fc_readptr_flags = 0;

		/*
		 * 如果框架头可能是可移动的，或者我们有一个排除子句，我们可能需要重新启动聚合...
		 */
		if (!(fc_frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING) ||
			(fc_frameOptions & FRAMEOPTION_EXCLUSION))
		{
			/* ... 因此创建一个标记指针跟踪框架头 */
			fc_agg_winobj->markptr = tuplestore_alloc_read_pointer(fc_winstate->buffer, 0);
			/* 而读取指针将需要向后能力 */
			fc_readptr_flags |= EXEC_FLAG_BACKWARD;
		}

		fc_agg_winobj->readptr = tuplestore_alloc_read_pointer(fc_winstate->buffer,
															fc_readptr_flags);
		fc_agg_winobj->markpos = -1;
		fc_agg_winobj->seekpos = -1;

		/* 也重置聚合的行计数器 */
		fc_winstate->aggregatedbase = 0;
		fc_winstate->aggregatedupto = 0;
	}

	/* 为每个实际窗口函数创建标记和读取指针 */
	for (fc_i = 0; fc_i < fc_numfuncs; fc_i++)
	{
		WindowStatePerFunc fc_perfuncstate = &(fc_winstate->perfunc[fc_i]);

		if (!fc_perfuncstate->plain_agg)
		{
			WindowObject fc_winobj = fc_perfuncstate->winobj;

			fc_winobj->markptr = tuplestore_alloc_read_pointer(fc_winstate->buffer,
															0);
			fc_winobj->readptr = tuplestore_alloc_read_pointer(fc_winstate->buffer,
															EXEC_FLAG_BACKWARD);
			fc_winobj->markpos = -1;
			fc_winobj->seekpos = -1;
		}
	}

	/*
	 * 如果我们处于 RANGE 或 GROUPS 模式，则确定框架边界需要物理访问框架端点行，除非在某些退化情况下。我们创建读取指针指向这些行，以简化访问并确保元组存储不会过早丢弃端点行。 (必须在更新 update_frameheadpos 和 update_frametailpos 需要它们的确切相同情况下创建指针。)
	 */
	fc_winstate->framehead_ptr = fc_winstate->frametail_ptr = -1; /* 如果未使用 */

	if (fc_frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS))
	{
		if (((fc_frameOptions & FRAMEOPTION_START_CURRENT_ROW) &&
			 fc_node->ordNumCols != 0) ||
			(fc_frameOptions & FRAMEOPTION_START_OFFSET))
			fc_winstate->framehead_ptr =
				tuplestore_alloc_read_pointer(fc_winstate->buffer, 0);
		if (((fc_frameOptions & FRAMEOPTION_END_CURRENT_ROW) &&
			 fc_node->ordNumCols != 0) ||
			(fc_frameOptions & FRAMEOPTION_END_OFFSET))
			fc_winstate->frametail_ptr =
				tuplestore_alloc_read_pointer(fc_winstate->buffer, 0);
	}

	/*
	 * 如果我们有一个排除子句，需要知道当前行的同级组的边界，我们创建一个读取指针跟踪同级组的尾部位置（即，下一同级组的第一行）。头部位置不需要自身的指针，因为我们在推进当前行时保持该位置。
	 */
	fc_winstate->grouptail_ptr = -1;

	if ((fc_frameOptions & (FRAMEOPTION_EXCLUDE_GROUP |
						 FRAMEOPTION_EXCLUDE_TIES)) &&
		fc_node->ordNumCols != 0)
	{
		fc_winstate->grouptail_ptr =
			tuplestore_alloc_read_pointer(fc_winstate->buffer, 0);
	}

	/*
	 * 将第一个元组存储到元组存储中（现在始终可用；我们要么在上面读取它，要么在上一个分区结束时保存它）
	 */
	tuplestore_puttupleslot(fc_winstate->buffer, fc_winstate->first_part_slot);
	fc_winstate->spooled_rows++;
}

/*
 * 从外部节点读取元组，直到包括位置 'pos'，并将它们存储到元组存储中。如果 pos 为 -1，则读取整个分区。
 */
static void fc_spool_tuples(WindowAggState *fc_winstate, int64 fc_pos)
{
	WindowAgg  *fc_node = (WindowAgg *) fc_winstate->ss.ps.plan;
	PlanState  *fc_outerPlan;
	TupleTableSlot *fc_outerslot;
	MemoryContext fc_oldcontext;

	if (!fc_winstate->buffer)
		return;					/* 只是一个安全检查 */
	if (fc_winstate->partition_spooled)
		return;					/* 整个分区已经完成 */

	/*
	 * 在直通模式下，我们可以完全消耗当前分区中的所有元组。我们不需要这些元组用于后续的窗口函数评估，然而，如果我们不是顶层窗口，作为另一个窗口聚合节点，上面的节点必须看到这些元组，因此我们需要将它们保留下来。
	 */
	if (fc_winstate->status != WINDOWAGG_RUN)
	{
		Assert(fc_winstate->status == WINDOWAGG_PASSTHROUGH ||
			   fc_winstate->status == WINDOWAGG_PASSTHROUGH_STRICT);

		fc_pos = -1;
	}

	/*
	 * 如果元组存储已溢出到磁盘，由于频繁的缓冲刷新，交替读写变得相当昂贵。一次性强制整个分区的输出更加便宜。
	 *
	 * XXX 这是一个可怕的权宜之计 —— 修复元组存储内部的性能问题会更好。修复
	 */
	else if (!tuplestore_in_memory(fc_winstate->buffer))
		fc_pos = -1;

	fc_outerPlan = outerPlanState(fc_winstate);

	/* 必须在查询上下文中才能调用 outerplan */
	fc_oldcontext = MemoryContextSwitchTo(fc_winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);

	while (fc_winstate->spooled_rows <= fc_pos || fc_pos == -1)
	{
		fc_outerslot = ExecProcNode(fc_outerPlan);
		if (TupIsNull(fc_outerslot))
		{
			/* 已到达最后一个分区的结束 */
			fc_winstate->partition_spooled = true;
			fc_winstate->more_partitions = false;
			break;
		}

		if (fc_node->partNumCols > 0)
		{
			ExprContext *fc_econtext = fc_winstate->tmpcontext;

			fc_econtext->ecxt_innertuple = fc_winstate->first_part_slot;
			fc_econtext->ecxt_outertuple = fc_outerslot;

			/* 检查这个元组是否仍然属于当前分区 */
			if (!ExecQualAndReset(fc_winstate->partEqfunction, fc_econtext))
			{
				/*
				 * 分区结束；复制元组以用于下一个周期。
				 */
				ExecCopySlot(fc_winstate->first_part_slot, fc_outerslot);
				fc_winstate->partition_spooled = true;
				fc_winstate->more_partitions = true;
				break;
			}
		}

		/*
		 * 记住元组，除非我们是顶层窗口，并且我们处于直通模式。
		 */
		if (fc_winstate->status != WINDOWAGG_PASSTHROUGH_STRICT)
		{
			/* 仍在分区中，因此将其保存到 tuplestore */
			tuplestore_puttupleslot(fc_winstate->buffer, fc_outerslot);
			fc_winstate->spooled_rows++;
		}
	}

	MemoryContextSwitchTo(fc_oldcontext);
}

/*
 * release_partition
 * 清除分区中保留的信息，包括
 * tuplestore 和聚合结果。
 */
static void fc_release_partition(WindowAggState *fc_winstate)
{
	int			fc_i;

	for (fc_i = 0; fc_i < fc_winstate->numfuncs; fc_i++)
	{
		WindowStatePerFunc fc_perfuncstate = &(fc_winstate->perfunc[fc_i]);

		/* 释放此窗口函数的任何分区本地状态 */
		if (fc_perfuncstate->winobj)
			fc_perfuncstate->winobj->localmem = NULL;
	}

	/*
	 * 释放所有分区本地内存（特别是我们可能在上述循环中
	 * 垃圾了指针的任何分区本地状态，以及
	 * 任何聚合临时数据）。我们不依赖于零售的 pfree，因为某些
	 * 聚合可能已分配我们没有直接指针的数据。
	 */
	MemoryContextResetAndDeleteChildren(fc_winstate->partcontext);
	MemoryContextResetAndDeleteChildren(fc_winstate->aggcontext);
	for (fc_i = 0; fc_i < fc_winstate->numaggs; fc_i++)
	{
		if (fc_winstate->peragg[fc_i].aggcontext != fc_winstate->aggcontext)
			MemoryContextResetAndDeleteChildren(fc_winstate->peragg[fc_i].aggcontext);
	}

	if (fc_winstate->buffer)
		tuplestore_end(fc_winstate->buffer);
	fc_winstate->buffer = NULL;
	fc_winstate->partition_spooled = false;
}

/*
 * row_is_in_frame
 * 根据我们的窗口框架规则确定一行是否在当前行的窗口框架中
 *
 * 调用者必须已经确定该行在分区中并将其提取到一个槽中。此函数只是封装了
 * 框架规则。
 *
 * 返回：
 * -1，如果该行超出框架且没有后续行可以在框架内
 * 0，如果该行超出框架但后续行可能在框架内
 * 1，如果该行在框架内
 *
 * 可能会覆盖 winstate->temp_slot_2。
 */
static int fc_row_is_in_frame(WindowAggState *fc_winstate, int64 fc_pos, TupleTableSlot *fc_slot)
{
	int			fc_frameOptions = fc_winstate->frameOptions;

	Assert(fc_pos >= 0);			/*
		 * 我们使用 CaseTestExpr 作为强制转换树的基础，
		 * 因为插入源值是非常便宜的。
		 */

	/*
	 * 首先，检查框架起始条件。我们不妨始终将其委托给
	 * update_frameheadpos；这不会增加任何显著成本。
	 */
	update_frameheadpos(fc_winstate);
	if (fc_pos < fc_winstate->frameheadpos)
		return 0;

	/*
	 * 到目前为止很好，现在检查框架结束条件。在这里，我们在简单情况下避免调用
	 * update_frametailpos，以免进一步排队元组。
	 */
	if (fc_frameOptions & FRAMEOPTION_END_CURRENT_ROW)
	{
		if (fc_frameOptions & FRAMEOPTION_ROWS)
		{
			/* 当前行后的行超出框架 */
			if (fc_pos > fc_winstate->currentpos)
				return -1;
		}
		else if (fc_frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS))
		{
			/* 后续行不是同级超出框架 */
			if (fc_pos > fc_winstate->currentpos &&
				!fc_are_peers(fc_winstate, fc_slot, fc_winstate->ss.ss_ScanTupleSlot))
				return -1;
		}
		else
			Assert(false);
	}
	else if (fc_frameOptions & FRAMEOPTION_END_OFFSET)
	{
		if (fc_frameOptions & FRAMEOPTION_ROWS)
		{
			int64		fc_offset = DatumGetInt64(fc_winstate->endOffsetValue);

			/* 当前行 + 偏移后的行超出框架 */
			if (fc_frameOptions & FRAMEOPTION_END_OFFSET_PRECEDING)
				fc_offset = -fc_offset;

			if (fc_pos > fc_winstate->currentpos + fc_offset)
				return -1;
		}
		else if (fc_frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS))
		{
			/* 难案例，因此委托给 update_frametailpos */
			update_frametailpos(fc_winstate);
			if (fc_pos >= fc_winstate->frametailpos)
				return -1;
		}
		else
			Assert(false);
	}

	/* 检查排除条款 */
	if (fc_frameOptions & FRAMEOPTION_EXCLUDE_CURRENT_ROW)
	{
		if (fc_pos == fc_winstate->currentpos)
			return 0;
	}
	else if ((fc_frameOptions & FRAMEOPTION_EXCLUDE_GROUP) ||
			 ((fc_frameOptions & FRAMEOPTION_EXCLUDE_TIES) &&
			  fc_pos != fc_winstate->currentpos))
	{
		WindowAgg  *fc_node = (WindowAgg *) fc_winstate->ss.ps.plan;

		/* 如果没有 ORDER BY，所有行彼此均为同级 */
		if (fc_node->ordNumCols == 0)
			return 0;
		/* 否则，检查组边界 */
		if (fc_pos >= fc_winstate->groupheadpos)
		{
			update_grouptailpos(fc_winstate);
			if (fc_pos < fc_winstate->grouptailpos)
				return 0;
		}
	}

	/* 如果我们到达这里，它就在框架内 */
	return 1;
}

/*
 * update_frameheadpos
 * 使 frameheadpos 对当前行有效
 *
 * 请注意，frameheadpos 的计算不考虑任何窗口排除
 * 条款；当前行和/或其同级在此目的上被视为框架的一部分，
 * 即使它们必须稍后被排除。
 *
 * 可能会覆盖 winstate->temp_slot_2。
 */
static void update_frameheadpos(WindowAggState *fc_winstate)
{
	WindowAgg  *fc_node = (WindowAgg *) fc_winstate->ss.ps.plan;
	int			fc_frameOptions = fc_winstate->frameOptions;
	MemoryContext fc_oldcontext;

	if (fc_winstate->framehead_valid)
		return;					/* 当前行已知 */

	/* 我们可能在短暂的上下文中被调用 */
	fc_oldcontext = MemoryContextSwitchTo(fc_winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);

	if (fc_frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING)
	{
		/* 在 UNBOUNDED PRECEDING 模式下，框架头始终是行 0 */
		fc_winstate->frameheadpos = 0;
		fc_winstate->framehead_valid = true;
	}
	else if (fc_frameOptions & FRAMEOPTION_START_CURRENT_ROW)
	{
		if (fc_frameOptions & FRAMEOPTION_ROWS)
		{
			/* 在 ROWS 模式下，框架头与当前相同 */
			fc_winstate->frameheadpos = fc_winstate->currentpos;
			fc_winstate->framehead_valid = true;
		}
		else if (fc_frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS))
		{
			/* 如果没有 ORDER BY，所有行彼此均为同级 */
			if (fc_node->ordNumCols == 0)
			{
				fc_winstate->frameheadpos = 0;
				fc_winstate->framehead_valid = true;
				MemoryContextSwitchTo(fc_oldcontext);
				return;
			}

			/*
			 * 在 RANGE 或 GROUPS START_CURRENT_ROW 模式下，框架头是
			 * 当前行的同级的第一行。我们保留最后已知框架头行的副本
			 * 在 framehead_slot 中，并根据需要推进。请注意，如果到达
			 * 分区末尾，我们将让 frameheadpos = end + 1，framehead_slot 为空。
			 */
			tuplestore_select_read_pointer(fc_winstate->buffer,
										   fc_winstate->framehead_ptr);
			if (fc_winstate->frameheadpos == 0 &&
				TupIsNull(fc_winstate->framehead_slot))
			{
				/* 如果我们尚未提取，将第一行提取到 framehead_slot 中 */
				if (!tuplestore_gettupleslot(fc_winstate->buffer, true, true,
											 fc_winstate->framehead_slot))
					elog(ERROR, "unexpected end of tuplestore");
			}

			while (!TupIsNull(fc_winstate->framehead_slot))
			{
				if (fc_are_peers(fc_winstate, fc_winstate->framehead_slot,
							  fc_winstate->ss.ss_ScanTupleSlot))
					break;		/* 此行是正确的框架头 */
				/* 请注意，即使提取失败，我们也会推进 frameheadpos */
				fc_winstate->frameheadpos++;
				fc_spool_tuples(fc_winstate, fc_winstate->frameheadpos);
				if (!tuplestore_gettupleslot(fc_winstate->buffer, true, true,
											 fc_winstate->framehead_slot))
					break;		/* 分区末尾 */
			}
			fc_winstate->framehead_valid = true;
		}
		else
			Assert(false);
	}
	else if (fc_frameOptions & FRAMEOPTION_START_OFFSET)
	{
		if (fc_frameOptions & FRAMEOPTION_ROWS)
		{
			/* 在 ROWS 模式下，边界在当前之前/之后物理上是 n */
			int64		fc_offset = DatumGetInt64(fc_winstate->startOffsetValue);

			if (fc_frameOptions & FRAMEOPTION_START_OFFSET_PRECEDING)
				fc_offset = -fc_offset;

			fc_winstate->frameheadpos = fc_winstate->currentpos + fc_offset;
			/* 框架头不能在第一行之前 */
			if (fc_winstate->frameheadpos < 0)
				fc_winstate->frameheadpos = 0;
			else if (fc_winstate->frameheadpos > fc_winstate->currentpos + 1)
			{
				/* 确保 frameheadpos 不超过分区的末尾 */
				fc_spool_tuples(fc_winstate, fc_winstate->frameheadpos - 1);
				if (fc_winstate->frameheadpos > fc_winstate->spooled_rows)
					fc_winstate->frameheadpos = fc_winstate->spooled_rows;
			}
			fc_winstate->framehead_valid = true;
		}
		else if (fc_frameOptions & FRAMEOPTION_RANGE)
		{
			/*
			 * 在 RANGE START_OFFSET 模式下，框架头是满足
			 * 相关于当前行的 in_range 约束的第一行。
			 * 我们保留最后已知框架头行的副本
			 * 在 framehead_slot 中，并根据需要推进。请注意，如果我们
			 * 到达分区末尾，我们将让 frameheadpos = end + 1，framehead_slot 为空。
			 */
			int			fc_sortCol = fc_node->ordColIdx[0];
			bool		fc_sub,
						fc_less;

			/* 我们必须有一个排序列 */
			Assert(fc_node->ordNumCols == 1);

			/* 预计算 in_range 检查的标志 */
			if (fc_frameOptions & FRAMEOPTION_START_OFFSET_PRECEDING)
				fc_sub = true;		/* 从当前行中减去 startOffset */
			else
				fc_sub = false;	/* add it */
			fc_less = false;		/* 通常，我们希望帧头 >= 总和 */
			/* 如果排序顺序是降序，则翻转两个标志 */
			if (!fc_winstate->inRangeAsc)
			{
				fc_sub = !fc_sub;
				fc_less = true;
			}

			tuplestore_select_read_pointer(fc_winstate->buffer,
										   fc_winstate->framehead_ptr);
			if (fc_winstate->frameheadpos == 0 &&
				TupIsNull(fc_winstate->framehead_slot))
			{
				/* 如果我们尚未提取，将第一行提取到 framehead_slot 中 */
				if (!tuplestore_gettupleslot(fc_winstate->buffer, true, true,
											 fc_winstate->framehead_slot))
					elog(ERROR, "unexpected end of tuplestore");
			}

			while (!TupIsNull(fc_winstate->framehead_slot))
			{
				Datum		fc_headval,
							fc_currval;
				bool		fc_headisnull,
							fc_currisnull;

				fc_headval = slot_getattr(fc_winstate->framehead_slot, fc_sortCol,
									   &fc_headisnull);
				fc_currval = slot_getattr(fc_winstate->ss.ss_ScanTupleSlot, fc_sortCol,
									   &fc_currisnull);
				if (fc_headisnull || fc_currisnull)
				{
					/* 行的顺序仅依赖于 nulls_first */
					if (fc_winstate->inRangeNullsFirst)
					{
						/* 如果头部为 null 而当前不为 null，则推进头部 */
						if (!fc_headisnull || fc_currisnull)
							break;
					}
					else
					{
						/* 如果头部不为 null 而当前为 null，则推进头部 */
						if (fc_headisnull || !fc_currisnull)
							break;
					}
				}
				else
				{
					if (DatumGetBool(FunctionCall5Coll(&fc_winstate->startInRangeFunc,
													   fc_winstate->inRangeColl,
													   fc_headval,
													   fc_currval,
													   fc_winstate->startOffsetValue,
													   BoolGetDatum(fc_sub),
													   BoolGetDatum(fc_less))))
						break;	/* 此行是正确的框架头 */
				}
				/* 请注意，即使提取失败，我们也会推进 frameheadpos */
				fc_winstate->frameheadpos++;
				fc_spool_tuples(fc_winstate, fc_winstate->frameheadpos);
				if (!tuplestore_gettupleslot(fc_winstate->buffer, true, true,
											 fc_winstate->framehead_slot))
					break;		/* 分区末尾 */
			}
			fc_winstate->framehead_valid = true;
		}
		else if (fc_frameOptions & FRAMEOPTION_GROUPS)
		{
			/*
			 * 在 GROUPS START_OFFSET 模式下，帧头是第一个满足偏移约束的
			 * 同伴组的第一行。我们在 framehead_slot 中保留最后已知帧头行的副本，
			 * 并根据需要推进。注意，如果我们到达分区的末尾，我们将使
			 * frameheadpos = end+1，并且 framehead_slot 为空。
			 */
			int64		fc_offset = DatumGetInt64(fc_winstate->startOffsetValue);
			int64		fc_minheadgroup;

			if (fc_frameOptions & FRAMEOPTION_START_OFFSET_PRECEDING)
				fc_minheadgroup = fc_winstate->currentgroup - fc_offset;
			else
				fc_minheadgroup = fc_winstate->currentgroup + fc_offset;

			tuplestore_select_read_pointer(fc_winstate->buffer,
										   fc_winstate->framehead_ptr);
			if (fc_winstate->frameheadpos == 0 &&
				TupIsNull(fc_winstate->framehead_slot))
			{
				/* 如果我们尚未提取，将第一行提取到 framehead_slot 中 */
				if (!tuplestore_gettupleslot(fc_winstate->buffer, true, true,
											 fc_winstate->framehead_slot))
					elog(ERROR, "unexpected end of tuplestore");
			}

			while (!TupIsNull(fc_winstate->framehead_slot))
			{
				if (fc_winstate->frameheadgroup >= fc_minheadgroup)
					break;		/* 此行是正确的框架头 */
				ExecCopySlot(fc_winstate->temp_slot_2, fc_winstate->framehead_slot);
				/* 请注意，即使提取失败，我们也会推进 frameheadpos */
				fc_winstate->frameheadpos++;
				fc_spool_tuples(fc_winstate, fc_winstate->frameheadpos);
				if (!tuplestore_gettupleslot(fc_winstate->buffer, true, true,
											 fc_winstate->framehead_slot))
					break;		/* 分区末尾 */
				if (!fc_are_peers(fc_winstate, fc_winstate->temp_slot_2,
							   fc_winstate->framehead_slot))
					fc_winstate->frameheadgroup++;
			}
			ExecClearTuple(fc_winstate->temp_slot_2);
			fc_winstate->framehead_valid = true;
		}
		else
			Assert(false);
	}
	else
		Assert(false);

	MemoryContextSwitchTo(fc_oldcontext);
}

/*
 * update_frametailpos
 * 使 frametailpos 对当前行有效
 *
 * 注意，frametailpos 的计算不考虑任何窗口排除
 * 条款；当前行和/或其同伴在此目的下被视为帧的一部分，
 * 即使它们在之后必须被排除。
 *
 * 可能会覆盖 winstate->temp_slot_2。
 */
static void update_frametailpos(WindowAggState *fc_winstate)
{
	WindowAgg  *fc_node = (WindowAgg *) fc_winstate->ss.ps.plan;
	int			fc_frameOptions = fc_winstate->frameOptions;
	MemoryContext fc_oldcontext;

	if (fc_winstate->frametail_valid)
		return;					/* 当前行已知 */

	/* 我们可能在短暂的上下文中被调用 */
	fc_oldcontext = MemoryContextSwitchTo(fc_winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);

	if (fc_frameOptions & FRAMEOPTION_END_UNBOUNDED_FOLLOWING)
	{
		/* 在 UNBOUNDED FOLLOWING 模式下，所有分区行都在帧内 */
		fc_spool_tuples(fc_winstate, -1);
		fc_winstate->frametailpos = fc_winstate->spooled_rows;
		fc_winstate->frametail_valid = true;
	}
	else if (fc_frameOptions & FRAMEOPTION_END_CURRENT_ROW)
	{
		if (fc_frameOptions & FRAMEOPTION_ROWS)
		{
			/* 在 ROWS 模式下，恰好到当前为止的行在帧内 */
			fc_winstate->frametailpos = fc_winstate->currentpos + 1;
			fc_winstate->frametail_valid = true;
		}
		else if (fc_frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS))
		{
			/* 如果没有 ORDER BY，所有行彼此均为同级 */
			if (fc_node->ordNumCols == 0)
			{
				fc_spool_tuples(fc_winstate, -1);
				fc_winstate->frametailpos = fc_winstate->spooled_rows;
				fc_winstate->frametail_valid = true;
				MemoryContextSwitchTo(fc_oldcontext);
				return;
			}

			/*
			 * 在 RANGE 或 GROUPS END_CURRENT_ROW 模式下，帧结束是当前行的
			 * 最后一行的同伴，帧尾是之后的行（如果有）。我们在 frametail_slot 中保留
			 * 最后已知帧尾行的副本，并根据需要推进。注意，如果我们到达分区的末尾，
			 * 我们将使 frametailpos = end+1，并且 frametail_slot 为空。
			 */
			tuplestore_select_read_pointer(fc_winstate->buffer,
										   fc_winstate->frametail_ptr);
			if (fc_winstate->frametailpos == 0 &&
				TupIsNull(fc_winstate->frametail_slot))
			{
				/* 如果我们还没有，则获取第一行到 frametail_slot */
				if (!tuplestore_gettupleslot(fc_winstate->buffer, true, true,
											 fc_winstate->frametail_slot))
					elog(ERROR, "unexpected end of tuplestore");
			}

			while (!TupIsNull(fc_winstate->frametail_slot))
			{
				if (fc_winstate->frametailpos > fc_winstate->currentpos &&
					!fc_are_peers(fc_winstate, fc_winstate->frametail_slot,
							   fc_winstate->ss.ss_ScanTupleSlot))
					break;		/* 该行是帧尾 */
				/* 注意即使抓取失败，我们也会推进 frametailpos */
				fc_winstate->frametailpos++;
				fc_spool_tuples(fc_winstate, fc_winstate->frametailpos);
				if (!tuplestore_gettupleslot(fc_winstate->buffer, true, true,
											 fc_winstate->frametail_slot))
					break;		/* 分区末尾 */
			}
			fc_winstate->frametail_valid = true;
		}
		else
			Assert(false);
	}
	else if (fc_frameOptions & FRAMEOPTION_END_OFFSET)
	{
		if (fc_frameOptions & FRAMEOPTION_ROWS)
		{
			/* 在 ROWS 模式下，边界在当前之前/之后物理上是 n */
			int64		fc_offset = DatumGetInt64(fc_winstate->endOffsetValue);

			if (fc_frameOptions & FRAMEOPTION_END_OFFSET_PRECEDING)
				fc_offset = -fc_offset;

			fc_winstate->frametailpos = fc_winstate->currentpos + fc_offset + 1;
			/* frametailpos 的最小允许值为 0 */
			if (fc_winstate->frametailpos < 0)
				fc_winstate->frametailpos = 0;
			else if (fc_winstate->frametailpos > fc_winstate->currentpos + 1)
			{
				/* 确保 frametailpos 不超过分区结束 */
				fc_spool_tuples(fc_winstate, fc_winstate->frametailpos - 1);
				if (fc_winstate->frametailpos > fc_winstate->spooled_rows)
					fc_winstate->frametailpos = fc_winstate->spooled_rows;
			}
			fc_winstate->frametail_valid = true;
		}
		else if (fc_frameOptions & FRAMEOPTION_RANGE)
		{
			/*
			 * 在 RANGE END_OFFSET 模式下，帧结束是最后一行
			 * 相对于当前行满足 in_range 约束条件，帧尾是
			 * 之后的行（如果有）。我们在 frametail_slot 中保留
			 * 最后已知帧尾行的副本，并根据需要推进。注意，如果我们达到
			 * 分区的末尾，我们将使 frametailpos = end+1，并且 frametail_slot 为空。
			 */
			int			fc_sortCol = fc_node->ordColIdx[0];
			bool		fc_sub,
						fc_less;

			/* 我们必须有一个排序列 */
			Assert(fc_node->ordNumCols == 1);

			/* 预计算 in_range 检查的标志 */
			if (fc_frameOptions & FRAMEOPTION_END_OFFSET_PRECEDING)
				fc_sub = true;		/* 从当前行中减去 endOffset */
			else
				fc_sub = false;	/* add it */
			fc_less = true;		/* 通常，我们希望帧尾 <= 总和 */
			/* 如果排序顺序是降序，则翻转两个标志 */
			if (!fc_winstate->inRangeAsc)
			{
				fc_sub = !fc_sub;
				fc_less = false;
			}

			tuplestore_select_read_pointer(fc_winstate->buffer,
										   fc_winstate->frametail_ptr);
			if (fc_winstate->frametailpos == 0 &&
				TupIsNull(fc_winstate->frametail_slot))
			{
				/* 如果我们还没有，则获取第一行到 frametail_slot */
				if (!tuplestore_gettupleslot(fc_winstate->buffer, true, true,
											 fc_winstate->frametail_slot))
					elog(ERROR, "unexpected end of tuplestore");
			}

			while (!TupIsNull(fc_winstate->frametail_slot))
			{
				Datum		fc_tailval,
							fc_currval;
				bool		fc_tailisnull,
							fc_currisnull;

				fc_tailval = slot_getattr(fc_winstate->frametail_slot, fc_sortCol,
									   &fc_tailisnull);
				fc_currval = slot_getattr(fc_winstate->ss.ss_ScanTupleSlot, fc_sortCol,
									   &fc_currisnull);
				if (fc_tailisnull || fc_currisnull)
				{
					/* 行的顺序仅依赖于 nulls_first */
					if (fc_winstate->inRangeNullsFirst)
					{
						/* 如果尾部是 null 或当前不为，推进尾部 */
						if (!fc_tailisnull)
							break;
					}
					else
					{
						/* 如果尾部不为 null 或当前为 null，推进尾部 */
						if (!fc_currisnull)
							break;
					}
				}
				else
				{
					if (!DatumGetBool(FunctionCall5Coll(&fc_winstate->endInRangeFunc,
														fc_winstate->inRangeColl,
														fc_tailval,
														fc_currval,
														fc_winstate->endOffsetValue,
														BoolGetDatum(fc_sub),
														BoolGetDatum(fc_less))))
						break;	/* 该行是正确的帧尾 */
				}
				/* 注意即使抓取失败，我们也会推进 frametailpos */
				fc_winstate->frametailpos++;
				fc_spool_tuples(fc_winstate, fc_winstate->frametailpos);
				if (!tuplestore_gettupleslot(fc_winstate->buffer, true, true,
											 fc_winstate->frametail_slot))
					break;		/* 分区末尾 */
			}
			fc_winstate->frametail_valid = true;
		}
		else if (fc_frameOptions & FRAMEOPTION_GROUPS)
		{
			/*
			 * 在 GROUPS END_OFFSET 模式下，帧结束是最后一行
			 * 符合偏移约束的最后一个同伴组，
			 * 帧尾是之后的行（如果有）。我们在 frametail_slot 中保留
			 * 最后已知帧尾行的副本，并根据需要推进。
			 * 注意，如果我们达到分区的末尾，我们将使
			 * frametailpos = end+1，并且 frametail_slot 为空。
			 */
			int64		fc_offset = DatumGetInt64(fc_winstate->endOffsetValue);
			int64		fc_maxtailgroup;

			if (fc_frameOptions & FRAMEOPTION_END_OFFSET_PRECEDING)
				fc_maxtailgroup = fc_winstate->currentgroup - fc_offset;
			else
				fc_maxtailgroup = fc_winstate->currentgroup + fc_offset;

			tuplestore_select_read_pointer(fc_winstate->buffer,
										   fc_winstate->frametail_ptr);
			if (fc_winstate->frametailpos == 0 &&
				TupIsNull(fc_winstate->frametail_slot))
			{
				/* 如果我们还没有，则获取第一行到 frametail_slot */
				if (!tuplestore_gettupleslot(fc_winstate->buffer, true, true,
											 fc_winstate->frametail_slot))
					elog(ERROR, "unexpected end of tuplestore");
			}

			while (!TupIsNull(fc_winstate->frametail_slot))
			{
				if (fc_winstate->frametailgroup > fc_maxtailgroup)
					break;		/* 该行是正确的帧尾 */
				ExecCopySlot(fc_winstate->temp_slot_2, fc_winstate->frametail_slot);
				/* 注意即使抓取失败，我们也会推进 frametailpos */
				fc_winstate->frametailpos++;
				fc_spool_tuples(fc_winstate, fc_winstate->frametailpos);
				if (!tuplestore_gettupleslot(fc_winstate->buffer, true, true,
											 fc_winstate->frametail_slot))
					break;		/* 分区末尾 */
				if (!fc_are_peers(fc_winstate, fc_winstate->temp_slot_2,
							   fc_winstate->frametail_slot))
					fc_winstate->frametailgroup++;
			}
			ExecClearTuple(fc_winstate->temp_slot_2);
			fc_winstate->frametail_valid = true;
		}
		else
			Assert(false);
	}
	else
		Assert(false);

	MemoryContextSwitchTo(fc_oldcontext);
}

/*
 * update_grouptailpos
 * 使 grouptailpos 对当前行有效
 *
 * 可能会覆盖 winstate->temp_slot_2。
 */
static void update_grouptailpos(WindowAggState *fc_winstate)
{
	WindowAgg  *fc_node = (WindowAgg *) fc_winstate->ss.ps.plan;
	MemoryContext fc_oldcontext;

	if (fc_winstate->grouptail_valid)
		return;					/* 当前行已知 */

	/* 我们可能在短暂的上下文中被调用 */
	fc_oldcontext = MemoryContextSwitchTo(fc_winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);

	/* 如果没有 ORDER BY，所有行彼此均为同级 */
	if (fc_node->ordNumCols == 0)
	{
		fc_spool_tuples(fc_winstate, -1);
		fc_winstate->grouptailpos = fc_winstate->spooled_rows;
		fc_winstate->grouptail_valid = true;
		MemoryContextSwitchTo(fc_oldcontext);
		return;
	}

	/*
	 * 由于 grouptail_valid 仅在当前行进入新的同伴组时被重置，
	 * 我们总是知道 grouptailpos 需要至少推进一行。因此，与
	 * 帧尾跟踪的其他类似情况不同，我们不需要对组尾行
	 * 进行持久存储。
	 */
	Assert(fc_winstate->grouptailpos <= fc_winstate->currentpos);
	tuplestore_select_read_pointer(fc_winstate->buffer,
								   fc_winstate->grouptail_ptr);
	for (;;)
	{
		/* 注意即使抓取失败，我们也会推进 grouptailpos */
		fc_winstate->grouptailpos++;
		fc_spool_tuples(fc_winstate, fc_winstate->grouptailpos);
		if (!tuplestore_gettupleslot(fc_winstate->buffer, true, true,
									 fc_winstate->temp_slot_2))
			break;				/* 分区末尾 */
		if (fc_winstate->grouptailpos > fc_winstate->currentpos &&
			!fc_are_peers(fc_winstate, fc_winstate->temp_slot_2,
					   fc_winstate->ss.ss_ScanTupleSlot))
			break;				/* 该行是组尾 */
	}
	ExecClearTuple(fc_winstate->temp_slot_2);
	fc_winstate->grouptail_valid = true;

	MemoryContextSwitchTo(fc_oldcontext);
}


/* -----------------
 * ExecWindowAgg
 *
 *	ExecWindowAgg 从其外部子计划接收元组并
 *	将它们存储到一个 tuple store 中，然后处理窗口函数。
 *	该节点不会减少或限定任何行，因此返回的行数
 *	与其外部子计划的结果完全相同。
 * -----------------
 */
static TupleTableSlot * fc_ExecWindowAgg(PlanState *fc_pstate)
{
	WindowAggState *fc_winstate = castNode(WindowAggState, fc_pstate);
	TupleTableSlot *fc_slot;
	ExprContext *fc_econtext;
	int			fc_i;
	int			fc_numfuncs;

	CHECK_FOR_INTERRUPTS();

	if (fc_winstate->status == WINDOWAGG_DONE)
		return NULL;

	/*
	 * 计算帧偏移值（如果有），在第一次调用时（或在
	 * 重新扫描后）。这些值假设在扫描过程中保持不变；
	 * 如果用户给我们一个易变的表达式，我们只会使用它的初始值。
	 */
	if (fc_winstate->all_first)
	{
		int			fc_frameOptions = fc_winstate->frameOptions;
		ExprContext *fc_econtext = fc_winstate->ss.ps.ps_ExprContext;
		Datum		fc_value;
		bool		fc_isnull;
		int16		fc_len;
		bool		fc_byval;

		if (fc_frameOptions & FRAMEOPTION_START_OFFSET)
		{
			Assert(fc_winstate->startOffset != NULL);
			fc_value = ExecEvalExprSwitchContext(fc_winstate->startOffset,
											  fc_econtext,
											  &fc_isnull);
			if (fc_isnull)
				ereport(ERROR,
						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
						 errmsg("frame starting offset must not be null")));
			/* 将值复制到查询生命周期上下文 */
			get_typlenbyval(exprType((Node *) fc_winstate->startOffset->expr),
							&fc_len, &fc_byval);
			fc_winstate->startOffsetValue = datumCopy(fc_value, fc_byval, fc_len);
			if (fc_frameOptions & (FRAMEOPTION_ROWS | FRAMEOPTION_GROUPS))
			{
				/* 值已知为 int8 */
				int64		fc_offset = DatumGetInt64(fc_value);

				if (fc_offset < 0)
					ereport(ERROR,
							(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
							 errmsg("frame starting offset must not be negative")));
			}
		}
		if (fc_frameOptions & FRAMEOPTION_END_OFFSET)
		{
			Assert(fc_winstate->endOffset != NULL);
			fc_value = ExecEvalExprSwitchContext(fc_winstate->endOffset,
											  fc_econtext,
											  &fc_isnull);
			if (fc_isnull)
				ereport(ERROR,
						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
						 errmsg("frame ending offset must not be null")));
			/* 将值复制到查询生命周期上下文 */
			get_typlenbyval(exprType((Node *) fc_winstate->endOffset->expr),
							&fc_len, &fc_byval);
			fc_winstate->endOffsetValue = datumCopy(fc_value, fc_byval, fc_len);
			if (fc_frameOptions & (FRAMEOPTION_ROWS | FRAMEOPTION_GROUPS))
			{
				/* 值已知为 int8 */
				int64		fc_offset = DatumGetInt64(fc_value);

				if (fc_offset < 0)
					ereport(ERROR,
							(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
							 errmsg("frame ending offset must not be negative")));
			}
		}
		fc_winstate->all_first = false;
	}

	/* 我们需要循环，因为 runCondition 或 qual 可能会过滤出元组 */
	for (;;)
	{
		if (fc_winstate->buffer == NULL)
		{
			/* 为第一个分区初始化并将当前行设置为 0 */
			fc_begin_partition(fc_winstate);
			/* 如果没有输入行，我们将在下面检测到并退出 */
		}
		else
		{
			/* 在分区内推进当前行 */
			fc_winstate->currentpos++;
			/* 这可能意味着框架也会移动 */
			fc_winstate->framehead_valid = false;
			fc_winstate->frametail_valid = false;
			/* 我们不需要在这里使 grouptail 无效；见下文 */
		}

		/*
		 * 如果我们还没有，缓存所有元组到当前行（包括当前行）
		 */
		fc_spool_tuples(fc_winstate, fc_winstate->currentpos);

		/* 如果到达此分区的末尾，则移动到下一个分区 */
		if (fc_winstate->partition_spooled &&
			fc_winstate->currentpos >= fc_winstate->spooled_rows)
		{
			fc_release_partition(fc_winstate);

			if (fc_winstate->more_partitions)
			{
				fc_begin_partition(fc_winstate);
				Assert(fc_winstate->spooled_rows > 0);

				/* 更改分区时退出通过模式 */
				fc_winstate->status = WINDOWAGG_RUN;
			}
			else
			{
				/* 没有进一步的分区？我们完成了 */
				fc_winstate->status = WINDOWAGG_DONE;
				return NULL;
			}
		}

		/* 最终输出执行在 ps_ExprContext 中 */
		fc_econtext = fc_winstate->ss.ps.ps_ExprContext;

		/* 清除当前行的每输出元组上下文 */
		ResetExprContext(fc_econtext);

		/*
		 * 从 tuplestore 中读取当前行并保存到 ScanTupleSlot。
		 * （我们不能依赖 outerplan 的输出槽，因为我们可能
		 * 必须读取超出当前行的位置。此外，我们必须
		 * 实际上从 tuplestore 中复制行，因为窗口函数
		 * 的评估可能导致 tuplestore 将其状态转储到
		 * 磁盘。）
		 *
		 * 在 GROUPS 模式下，或者在跟踪面向组的排除子句时，
		 * 我们还必须检测到进入新的同伴组，并在发生时更新相关状态。
		 * 我们使用 temp_slot_2 暂时保存上一行以达到此目的。
		 *
		 * 当前行必须在 tuplestore 中，因为我们在上面缓冲了它。
		 */
		tuplestore_select_read_pointer(fc_winstate->buffer, fc_winstate->current_ptr);
		if ((fc_winstate->frameOptions & (FRAMEOPTION_GROUPS |
									   FRAMEOPTION_EXCLUDE_GROUP |
									   FRAMEOPTION_EXCLUDE_TIES)) &&
			fc_winstate->currentpos > 0)
		{
			ExecCopySlot(fc_winstate->temp_slot_2, fc_winstate->ss.ss_ScanTupleSlot);
			if (!tuplestore_gettupleslot(fc_winstate->buffer, true, true,
										 fc_winstate->ss.ss_ScanTupleSlot))
				elog(ERROR, "unexpected end of tuplestore");
			if (!fc_are_peers(fc_winstate, fc_winstate->temp_slot_2,
						   fc_winstate->ss.ss_ScanTupleSlot))
			{
				fc_winstate->currentgroup++;
				fc_winstate->groupheadpos = fc_winstate->currentpos;
				fc_winstate->grouptail_valid = false;
			}
			ExecClearTuple(fc_winstate->temp_slot_2);
		}
		else
		{
			if (!tuplestore_gettupleslot(fc_winstate->buffer, true, true,
										 fc_winstate->ss.ss_ScanTupleSlot))
				elog(ERROR, "unexpected end of tuplestore");
		}

		/* 在通过模式下时不评估窗口函数 */
		if (fc_winstate->status == WINDOWAGG_RUN)
		{
			/*
			 * 评估真正的窗口函数
			 */
			fc_numfuncs = fc_winstate->numfuncs;
			for (fc_i = 0; fc_i < fc_numfuncs; fc_i++)
			{
				WindowStatePerFunc fc_perfuncstate = &(fc_winstate->perfunc[fc_i]);

				if (fc_perfuncstate->plain_agg)
					continue;
				fc_eval_windowfunction(fc_winstate, fc_perfuncstate,
									&(fc_econtext->ecxt_aggvalues[fc_perfuncstate->wfuncstate->wfuncno]),
									&(fc_econtext->ecxt_aggnulls[fc_perfuncstate->wfuncstate->wfuncno]));
			}

			/*
			 * 评估聚合
			 */
			if (fc_winstate->numaggs > 0)
				fc_eval_windowaggregates(fc_winstate);
		}

		/*
		 * 如果我们为框架或组边界创建了辅助读取指针，
		 * 则强制它们保持最新状态，因为我们不知道
		 * 窗口函数是否会做任何需要的事。
		 * 如果无法推进指针，将导致无法
		 * 从 tuplestore 中修剪数据，这是糟糕的。（如果我们可以提前知道
		 * 窗口函数是否会使用框架边界信息，
		 * 我们可以跳过首先创建这些指针……但是
		 * 不幸的是，窗口函数 API 并不要求这样。）
		 */
		if (fc_winstate->framehead_ptr >= 0)
			update_frameheadpos(fc_winstate);
		if (fc_winstate->frametail_ptr >= 0)
			update_frametailpos(fc_winstate);
		if (fc_winstate->grouptail_ptr >= 0)
			update_grouptailpos(fc_winstate);

		/*
		 * 从 tuplestore 裁剪任何不再需要的行。
		 */
		tuplestore_trim(fc_winstate->buffer);

		/*
		 * 使用窗口函数结果和当前行形成并返回一个投影元组。
		 * 设置 ecxt_outertuple 会安排以确保任何 Vars
		 * 都会相对于该行进行评估。
		 */
		fc_econtext->ecxt_outertuple = fc_winstate->ss.ss_ScanTupleSlot;

		fc_slot = ExecProject(fc_winstate->ss.ps.ps_ProjInfo);

		if (fc_winstate->status == WINDOWAGG_RUN)
		{
			fc_econtext->ecxt_scantuple = fc_slot;

			/*
			 * 现在评估运行条件，以查看我们是否需要进入
			 * 通过模式，或者可能完全停止。
			 */
			if (!ExecQual(fc_winstate->runcondition, fc_econtext))
			{
				/*
				 * 确定要进入哪种模式。如果没有
				 * PARTITION BY 子句并且我们是顶层 WindowAgg，
				 * 那么我们完成了。此元组和任何未来的元组都不可能
				 * 匹配 runcondition。然而，当有
				 * PARTITION BY 子句或我们不是顶层窗口时，
				 * 我们不能仅仅停止，因为我们需要处理其他
				 * 分区或确保我们上面的 WindowAgg 节点接收到
				 * 处理其 WindowFuncs 所需的所有元组。
				 */
				if (fc_winstate->use_pass_through)
				{
					/*
					 * 在切换到通过模式时，我们最好将
					 * 聚合结果置为 NULL，因为这些不再
					 * 更新，置为 NULL 可以避免旧的不新鲜的
					 * 结果残留。这些中的一些可能是 byref 类型，
					 * 因此我们不能让它们指向释放的内存。
					 * 计划者坚称 runcondition 中使用的 qulals
					 * 是严格的，因此顶层 WindowAgg 将始终
					 * 过滤这些 NULL。
					 */
					fc_numfuncs = fc_winstate->numfuncs;
					for (fc_i = 0; fc_i < fc_numfuncs; fc_i++)
					{
						fc_econtext->ecxt_aggvalues[fc_i] = (Datum) 0;
						fc_econtext->ecxt_aggnulls[fc_i] = true;
					}

					/*
					 * 在存在 PARTITION BY 子句时，顶层窗口需要严格通过模式。
					 * 否则，我们必须确保存储不匹配的元组
					 * runcondition，以便它们可以在上面的 WindowAgg 可用。
					 */
					if (fc_winstate->top_window)
					{
						fc_winstate->status = WINDOWAGG_PASSTHROUGH_STRICT;
						continue;
					}
					else
					{
						fc_winstate->status = WINDOWAGG_PASSTHROUGH;
					}
				}
				else
				{
					/*
					 * 不需要通过模式。我们可以直接返回 NULL。
					 * 其他的将不匹配 runcondition。
					 */
					fc_winstate->status = WINDOWAGG_DONE;
					return NULL;
				}
			}

			/*
			 * 过滤掉我们在顶层 WindowAgg 中不需要的元组。
			 */
			if (!ExecQual(fc_winstate->ss.ps.qual, fc_econtext))
			{
				InstrCountFiltered1(fc_winstate, 1);
				continue;
			}

			break;
		}

		/*
		 * 当不在 WINDOWAGG_RUN 模式下时，如果
		 * 我们不是顶层窗口，仍必须返回此元组。
		 */
		else if (!fc_winstate->top_window)
			break;
	}

	return fc_slot;
}

/* -----------------
 * ExecInitWindowAgg
 *
 * 创建计划者生成的 WindowAgg 节点的运行时信息并初始化其外部子树
 * -----------------
 */
WindowAggState *
ExecInitWindowAgg(WindowAgg *fc_node, EState *fc_estate, int fc_eflags)
{
	WindowAggState *fc_winstate;
	Plan	   *fc_outerPlan;
	ExprContext *fc_econtext;
	ExprContext *fc_tmpcontext;
	WindowStatePerFunc fc_perfunc;
	WindowStatePerAgg fc_peragg;
	int			fc_frameOptions = fc_node->frameOptions;
	int			fc_numfuncs,
				fc_wfuncno,
				fc_numaggs,
				fc_aggno;
	TupleDesc	fc_scanDesc;
	ListCell   *fc_l;

	/* 检查不支持的标志 */
	Assert(!(fc_eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));

	/*
	 * 创建状态结构
	 */
	fc_winstate = makeNode(WindowAggState);
	fc_winstate->ss.ps.plan = (Plan *) fc_node;
	fc_winstate->ss.ps.state = fc_estate;
	fc_winstate->ss.ps.ExecProcNode = fc_ExecWindowAgg;

	/* 将框架选项复制到状态节点以便于访问 */
	fc_winstate->frameOptions = fc_frameOptions;

	/*
	 * 创建表达式上下文。我们需要两个，一个用于每输入元组的处理，
	 * 一个用于每输出元组的处理。我们有点钻空子
	 * 通过使用 ExecAssignExprContext() 构建两个。
	 */
	ExecAssignExprContext(fc_estate, &fc_winstate->ss.ps);
	fc_tmpcontext = fc_winstate->ss.ps.ps_ExprContext;
	fc_winstate->tmpcontext = fc_tmpcontext;
	ExecAssignExprContext(fc_estate, &fc_winstate->ss.ps);

	/* 创建用于存储分区本地内存等的长久上下文 */
	fc_winstate->partcontext =
		AllocSetContextCreate(CurrentMemoryContext,
							  "WindowAgg Partition",
							  ALLOCSET_DEFAULT_SIZES);

	/*
	 * 创建用于汇总传输值等的中等生命周期上下文。
	 *
	 * 请注意，移动聚合每个使用自己的私有上下文，而不是
	 * 这个上下文。
	 */
	fc_winstate->aggcontext =
		AllocSetContextCreate(CurrentMemoryContext,
							  "WindowAgg Aggregates",
							  ALLOCSET_DEFAULT_SIZES);

	/* 只有顶层 WindowAgg 可能有 qual */
	Assert(fc_node->plan.qual == NIL || fc_node->topWindow);

	/* 初始化 qual */
	fc_winstate->ss.ps.qual = ExecInitQual(fc_node->plan.qual,
										(PlanState *) fc_winstate);

	/*
	 * 设置运行条件，如果我们从查询优化器接收到了条件。
	 * 设置后，这可能允许我们进入直通模式，以便我们
	 * 不必在当前分区中进一步评估 WindowFuncs，或者在所有
	 * 元组都在同一分区时可能完全停止返回元组。
	 */
	fc_winstate->runcondition = ExecInitQual(fc_node->runCondition,
										  (PlanState *) fc_winstate);

	/*
	 * 当我们不是顶层 WindowAgg 节点时，或者我们是但有
	 * PARTITION BY 子句时，当运行条件变为 false 时，我们必须进入
	 * WINDOWAGG_PASSTHROUGH* 模式之一。
	 */
	fc_winstate->use_pass_through = !fc_node->topWindow || fc_node->partNumCols > 0;

	/* 记住我们是否是顶层窗口或在顶层窗口下 */
	fc_winstate->top_window = fc_node->topWindow;

	/*
	 * 初始化子节点
	 */
	fc_outerPlan = outerPlan(fc_node);
	outerPlanState(fc_winstate) = ExecInitNode(fc_outerPlan, fc_estate, fc_eflags);

	/*
	 * 初始化源元组类型（这也是我们将在
	 * tuplestore 中存储并在所有工作槽中使用的元组类型）。
	 */
	ExecCreateScanSlotFromOuterPlan(fc_estate, &fc_winstate->ss, &TTSOpsMinimalTuple);
	fc_scanDesc = fc_winstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor;

	/* 外部元组不是子元组，而始终是最小元组 */
	fc_winstate->ss.ps.outeropsset = true;
	fc_winstate->ss.ps.outerops = &TTSOpsMinimalTuple;
	fc_winstate->ss.ps.outeropsfixed = true;

	/*
	 * 元组表初始化
	 */
	fc_winstate->first_part_slot = ExecInitExtraTupleSlot(fc_estate, fc_scanDesc,
													   &TTSOpsMinimalTuple);
	fc_winstate->agg_row_slot = ExecInitExtraTupleSlot(fc_estate, fc_scanDesc,
													&TTSOpsMinimalTuple);
	fc_winstate->temp_slot_1 = ExecInitExtraTupleSlot(fc_estate, fc_scanDesc,
												   &TTSOpsMinimalTuple);
	fc_winstate->temp_slot_2 = ExecInitExtraTupleSlot(fc_estate, fc_scanDesc,
												   &TTSOpsMinimalTuple);

	/*
	 * 仅在需要时创建帧头和尾槽（必须在
	 * update_frameheadpos 和 update_frametailpos
	 * 需要它们的确切情况中创建槽）。
	 */
	fc_winstate->framehead_slot = fc_winstate->frametail_slot = NULL;

	if (fc_frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS))
	{
		if (((fc_frameOptions & FRAMEOPTION_START_CURRENT_ROW) &&
			 fc_node->ordNumCols != 0) ||
			(fc_frameOptions & FRAMEOPTION_START_OFFSET))
			fc_winstate->framehead_slot = ExecInitExtraTupleSlot(fc_estate, fc_scanDesc,
															  &TTSOpsMinimalTuple);
		if (((fc_frameOptions & FRAMEOPTION_END_CURRENT_ROW) &&
			 fc_node->ordNumCols != 0) ||
			(fc_frameOptions & FRAMEOPTION_END_OFFSET))
			fc_winstate->frametail_slot = ExecInitExtraTupleSlot(fc_estate, fc_scanDesc,
															  &TTSOpsMinimalTuple);
	}

	/*
	 * 初始化结果槽、类型和投影。
	 */
	ExecInitResultTupleSlotTL(&fc_winstate->ss.ps, &TTSOpsVirtual);
	ExecAssignProjectionInfo(&fc_winstate->ss.ps, NULL);

	/* 设置比较元组的数据 */
	if (fc_node->partNumCols > 0)
		fc_winstate->partEqfunction =
			execTuplesMatchPrepare(fc_scanDesc,
								   fc_node->partNumCols,
								   fc_node->partColIdx,
								   fc_node->partOperators,
								   fc_node->partCollations,
								   &fc_winstate->ss.ps);

	if (fc_node->ordNumCols > 0)
		fc_winstate->ordEqfunction =
			execTuplesMatchPrepare(fc_scanDesc,
								   fc_node->ordNumCols,
								   fc_node->ordColIdx,
								   fc_node->ordOperators,
								   fc_node->ordCollations,
								   &fc_winstate->ss.ps);

	/*
	 * WindowAgg 节点使用 aggvalues 和 aggnulls 以及 Agg 节点。
	 */
	fc_numfuncs = fc_winstate->numfuncs;
	fc_numaggs = fc_winstate->numaggs;
	fc_econtext = fc_winstate->ss.ps.ps_ExprContext;
	fc_econtext->ecxt_aggvalues = (Datum *) palloc0(sizeof(Datum) * fc_numfuncs);
	fc_econtext->ecxt_aggnulls = (bool *) palloc0(sizeof(bool) * fc_numfuncs);

	/*
	 * 为每个 wfunc/per-agg 分配状态信息。
	 */
	fc_perfunc = (WindowStatePerFunc) palloc0(sizeof(WindowStatePerFuncData) * fc_numfuncs);
	fc_peragg = (WindowStatePerAgg) palloc0(sizeof(WindowStatePerAggData) * fc_numaggs);
	fc_winstate->perfunc = fc_perfunc;
	fc_winstate->peragg = fc_peragg;

	fc_wfuncno = -1;
	fc_aggno = -1;
	foreach(fc_l, fc_winstate->funcs)
	{
		WindowFuncExprState *fc_wfuncstate = (WindowFuncExprState *) lfirst(fc_l);
		WindowFunc *fc_wfunc = fc_wfuncstate->wfunc;
		WindowStatePerFunc fc_perfuncstate;
		AclResult	fc_aclresult;
		int			fc_i;

		if (fc_wfunc->winref != fc_node->winref)	/* 规划者搞砸了吗？ */
			elog(ERROR, "WindowFunc with winref %u assigned to WindowAgg with winref %u",
				 fc_wfunc->winref, fc_node->winref);

		/* 查找之前的重复窗口函数 */
		for (fc_i = 0; fc_i <= fc_wfuncno; fc_i++)
		{
			if (equal(fc_wfunc, fc_perfunc[fc_i].wfunc) &&
				!contain_volatile_functions((Node *) fc_wfunc))
				break;
		}
		if (fc_i <= fc_wfuncno)
		{
			/* 找到与现有条目匹配的项，因此只需标记它 */
			fc_wfuncstate->wfuncno = fc_i;
			continue;
		}

		/* 不，分配一个新的 PerAgg 记录 */
		fc_perfuncstate = &fc_perfunc[++fc_wfuncno];

		/* 使用结果数组中的分配索引标记 WindowFunc 状态节点 */
		fc_wfuncstate->wfuncno = fc_wfuncno;

		/* 检查调用窗口函数的权限 */
		fc_aclresult = pg_proc_aclcheck(fc_wfunc->winfnoid, GetUserId(),
									 ACL_EXECUTE);
		if (fc_aclresult != ACLCHECK_OK)
			aclcheck_error(fc_aclresult, OBJECT_FUNCTION,
						   get_func_name(fc_wfunc->winfnoid));
		InvokeFunctionExecuteHook(fc_wfunc->winfnoid);

		/* 填充 perfuncstate 数据 */
		fc_perfuncstate->wfuncstate = fc_wfuncstate;
		fc_perfuncstate->wfunc = fc_wfunc;
		fc_perfuncstate->numArguments = list_length(fc_wfuncstate->args);
		fc_perfuncstate->winCollation = fc_wfunc->inputcollid;

		get_typlenbyval(fc_wfunc->wintype,
						&fc_perfuncstate->resulttypeLen,
						&fc_perfuncstate->resulttypeByVal);

		/*
		 * 如果它真的只是一个普通的聚合函数，我们将模拟
		 * Agg 环境来处理它。
		 */
		fc_perfuncstate->plain_agg = fc_wfunc->winagg;
		if (fc_wfunc->winagg)
		{
			WindowStatePerAgg fc_peraggstate;

			fc_perfuncstate->aggno = ++fc_aggno;
			fc_peraggstate = &fc_winstate->peragg[fc_aggno];
			fc_initialize_peragg(fc_winstate, fc_wfunc, fc_peraggstate);
			fc_peraggstate->wfuncno = fc_wfuncno;
		}
		else
		{
			WindowObject fc_winobj = makeNode(WindowObjectData);

			fc_winobj->winstate = fc_winstate;
			fc_winobj->argstates = fc_wfuncstate->args;
			fc_winobj->localmem = NULL;
			fc_perfuncstate->winobj = fc_winobj;

			/* 它是一个真正的窗口函数，因此设置调用它。 */
			fmgr_info_cxt(fc_wfunc->winfnoid, &fc_perfuncstate->flinfo,
						  fc_econtext->ecxt_per_query_memory);
			fmgr_info_set_expr((Node *) fc_wfunc, &fc_perfuncstate->flinfo);
		}
	}

	/* 更新 numfuncs, numaggs 以匹配找到的唯一函数数量 */
	fc_winstate->numfuncs = fc_wfuncno + 1;
	fc_winstate->numaggs = fc_aggno + 1;

	/* 如果需要，则设置聚合的 WindowObject */
	if (fc_winstate->numaggs > 0)
	{
		WindowObject fc_agg_winobj = makeNode(WindowObjectData);

		fc_agg_winobj->winstate = fc_winstate;
		fc_agg_winobj->argstates = NIL;
		fc_agg_winobj->localmem = NULL;
		/* 确保 markptr = -1 以使其无效。它可能不会被使用 */
		fc_agg_winobj->markptr = -1;
		fc_agg_winobj->readptr = -1;
		fc_winstate->agg_winobj = fc_agg_winobj;
	}

	/* 设置状态为运行中 */
	fc_winstate->status = WINDOWAGG_RUN;

	/* 初始化帧边界偏移表达式 */
	fc_winstate->startOffset = ExecInitExpr((Expr *) fc_node->startOffset,
										 (PlanState *) fc_winstate);
	fc_winstate->endOffset = ExecInitExpr((Expr *) fc_node->endOffset,
									   (PlanState *) fc_winstate);

	/* 如果需要，查找 in_range 支持函数 */
	if (OidIsValid(fc_node->startInRangeFunc))
		fmgr_info(fc_node->startInRangeFunc, &fc_winstate->startInRangeFunc);
	if (OidIsValid(fc_node->endInRangeFunc))
		fmgr_info(fc_node->endInRangeFunc, &fc_winstate->endInRangeFunc);
	fc_winstate->inRangeColl = fc_node->inRangeColl;
	fc_winstate->inRangeAsc = fc_node->inRangeAsc;
	fc_winstate->inRangeNullsFirst = fc_node->inRangeNullsFirst;

	fc_winstate->all_first = true;
	fc_winstate->partition_spooled = false;
	fc_winstate->more_partitions = false;

	return fc_winstate;
}

/* -----------------
 * ExecEndWindowAgg
 * -----------------
 */
void ExecEndWindowAgg(WindowAggState *fc_node)
{
	PlanState  *fc_outerPlan;
	int			fc_i;

	fc_release_partition(fc_node);

	ExecClearTuple(fc_node->ss.ss_ScanTupleSlot);
	ExecClearTuple(fc_node->first_part_slot);
	ExecClearTuple(fc_node->agg_row_slot);
	ExecClearTuple(fc_node->temp_slot_1);
	ExecClearTuple(fc_node->temp_slot_2);
	if (fc_node->framehead_slot)
		ExecClearTuple(fc_node->framehead_slot);
	if (fc_node->frametail_slot)
		ExecClearTuple(fc_node->frametail_slot);

	/*
	 * 释放两个表达式上下文。
	 */
	ExecFreeExprContext(&fc_node->ss.ps);
	fc_node->ss.ps.ps_ExprContext = fc_node->tmpcontext;
	ExecFreeExprContext(&fc_node->ss.ps);

	for (fc_i = 0; fc_i < fc_node->numaggs; fc_i++)
	{
		if (fc_node->peragg[fc_i].aggcontext != fc_node->aggcontext)
			MemoryContextDelete(fc_node->peragg[fc_i].aggcontext);
	}
	MemoryContextDelete(fc_node->partcontext);
	MemoryContextDelete(fc_node->aggcontext);

	pfree(fc_node->perfunc);
	pfree(fc_node->peragg);

	fc_outerPlan = outerPlanState(fc_node);
	ExecEndNode(fc_outerPlan);
}

/* -----------------
 * ExecReScanWindowAgg
 * -----------------
 */
void ExecReScanWindowAgg(WindowAggState *fc_node)
{
	PlanState  *fc_outerPlan = outerPlanState(fc_node);
	ExprContext *fc_econtext = fc_node->ss.ps.ps_ExprContext;

	fc_node->status = WINDOWAGG_RUN;
	fc_node->all_first = true;

	/* 释放 tuplestore 等 */
	fc_release_partition(fc_node);

	/* 释放所有临时元组，特别是 first_part_slot */
	ExecClearTuple(fc_node->ss.ss_ScanTupleSlot);
	ExecClearTuple(fc_node->first_part_slot);
	ExecClearTuple(fc_node->agg_row_slot);
	ExecClearTuple(fc_node->temp_slot_1);
	ExecClearTuple(fc_node->temp_slot_2);
	if (fc_node->framehead_slot)
		ExecClearTuple(fc_node->framehead_slot);
	if (fc_node->frametail_slot)
		ExecClearTuple(fc_node->frametail_slot);

	/* 忘记当前 wfunc 值 */
	MemSet(fc_econtext->ecxt_aggvalues, 0, sizeof(Datum) * fc_node->numfuncs);
	MemSet(fc_econtext->ecxt_aggnulls, 0, sizeof(bool) * fc_node->numfuncs);

	/*
	 * 如果子节点的chgParam不为空，则计划将由第一个ExecProcNode重新扫描。
	 */
	if (fc_outerPlan->chgParam == NULL)
		ExecReScan(fc_outerPlan);
}

/*
 * initialize_peragg
 *
 * 与 nodeAgg.c 中几乎相同，除了我们当前不支持 DISTINCT。
 */
static WindowStatePerAggData *
fc_initialize_peragg(WindowAggState *fc_winstate, WindowFunc *fc_wfunc,
				  WindowStatePerAgg fc_peraggstate)
{
	Oid			fc_inputTypes[FUNC_MAX_ARGS];
	int			fc_numArguments;
	HeapTuple	fc_aggTuple;
	Form_pg_aggregate fc_aggform;
	Oid			fc_aggtranstype;
	AttrNumber	fc_initvalAttNo;
	AclResult	fc_aclresult;
	bool		fc_use_ma_code;
	Oid			fc_transfn_oid,
				fc_invtransfn_oid,
				fc_finalfn_oid;
	bool		fc_finalextra;
	char		fc_finalmodify;
	Expr	   *fc_transfnexpr,
			   *fc_invtransfnexpr,
			   *fc_finalfnexpr;
	Datum		fc_textInitVal;
	int			fc_i;
	ListCell   *fc_lc;

	fc_numArguments = list_length(fc_wfunc->args);

	fc_i = 0;
	foreach(fc_lc, fc_wfunc->args)
	{
		fc_inputTypes[fc_i++] = exprType((Node *) lfirst(fc_lc));
	}

	fc_aggTuple = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(fc_wfunc->winfnoid));
	if (!HeapTupleIsValid(fc_aggTuple))
		elog(ERROR, "cache lookup failed for aggregate %u",
			 fc_wfunc->winfnoid);
	fc_aggform = (Form_pg_aggregate) GETSTRUCT(fc_aggTuple);

	/*
	 * 确定我们是否希望使用移动汇总实现，
	 * 并从 pg_aggregate 条目收集正确的字段集。
	 *
	 * 可能存在一个聚合提供一个安全的移动汇总实现和一个不安全的正常实现，
	 * 在这种情况下，我们的选择受到限制。否则，如果帧头无法移动，
	 * 我们不需要移动汇总代码。即使我们希望使用它，如果聚合的参数
	 * （以及任何 FILTER 子句）包含对易失性函数的调用，也不要这样做。
	 * 否则，重新开始和不重新开始聚合之间的区别将对用户可见。
	 *
	 * 当参数或 FILTER 子句中存在子计划时，我们也不冒险使用移动聚合。
	 * 部分原因是 contain_volatile_functions() 不查看子计划；
	 * 但是还有其他原因，可能导致子计划的输出是易失性的。
	 * 例如，同步扫描模式可能使结果不可重复。
	 */
	if (!OidIsValid(fc_aggform->aggminvtransfn))
		fc_use_ma_code = false;	/* 不可或缺的因素 */
	else if (fc_aggform->aggmfinalmodify == AGGMODIFY_READ_ONLY &&
		fc_aggform->aggfinalmodify != AGGMODIFY_READ_ONLY)
		fc_use_ma_code = true;		/* 由于安全性决定的强制 */
	else if (fc_winstate->frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING)
		fc_use_ma_code = false;	/* 非移动帧头 */
	else if (contain_volatile_functions((Node *) fc_wfunc))
		fc_use_ma_code = false;	/* 避免可能的行为变化 */
	else if (contain_subplans((Node *) fc_wfunc))
		fc_use_ma_code = false;	/* 子计划可能包含易失性函数 */
	else
		fc_use_ma_code = true;		/* 是的，让我们使用它 */
	if (fc_use_ma_code)
	{
		fc_peraggstate->transfn_oid = fc_transfn_oid = fc_aggform->aggmtransfn;
		fc_peraggstate->invtransfn_oid = fc_invtransfn_oid = fc_aggform->aggminvtransfn;
		fc_peraggstate->finalfn_oid = fc_finalfn_oid = fc_aggform->aggmfinalfn;
		fc_finalextra = fc_aggform->aggmfinalextra;
		fc_finalmodify = fc_aggform->aggmfinalmodify;
		fc_aggtranstype = fc_aggform->aggmtranstype;
		fc_initvalAttNo = Anum_pg_aggregate_aggminitval;
	}
	else
	{
		fc_peraggstate->transfn_oid = fc_transfn_oid = fc_aggform->aggtransfn;
		fc_peraggstate->invtransfn_oid = fc_invtransfn_oid = InvalidOid;
		fc_peraggstate->finalfn_oid = fc_finalfn_oid = fc_aggform->aggfinalfn;
		fc_finalextra = fc_aggform->aggfinalextra;
		fc_finalmodify = fc_aggform->aggfinalmodify;
		fc_aggtranstype = fc_aggform->aggtranstype;
		fc_initvalAttNo = Anum_pg_aggregate_agginitval;
	}

	/*
	 * ExecInitWindowAgg 已经检查了调用汇总函数的权限
	 * ... 但我们仍然需要检查组件函数
	 */

	/* 检查聚合所有者是否有权限调用组件函数 */
	{
		HeapTuple	fc_procTuple;
		Oid			fc_aggOwner;

		fc_procTuple = SearchSysCache1(PROCOID,
									ObjectIdGetDatum(fc_wfunc->winfnoid));
		if (!HeapTupleIsValid(fc_procTuple))
			elog(ERROR, "cache lookup failed for function %u",
				 fc_wfunc->winfnoid);
		fc_aggOwner = ((Form_pg_proc) GETSTRUCT(fc_procTuple))->proowner;
		ReleaseSysCache(fc_procTuple);

		fc_aclresult = pg_proc_aclcheck(fc_transfn_oid, fc_aggOwner,
									 ACL_EXECUTE);
		if (fc_aclresult != ACLCHECK_OK)
			aclcheck_error(fc_aclresult, OBJECT_FUNCTION,
						   get_func_name(fc_transfn_oid));
		InvokeFunctionExecuteHook(fc_transfn_oid);

		if (OidIsValid(fc_invtransfn_oid))
		{
			fc_aclresult = pg_proc_aclcheck(fc_invtransfn_oid, fc_aggOwner,
										 ACL_EXECUTE);
			if (fc_aclresult != ACLCHECK_OK)
				aclcheck_error(fc_aclresult, OBJECT_FUNCTION,
							   get_func_name(fc_invtransfn_oid));
			InvokeFunctionExecuteHook(fc_invtransfn_oid);
		}

		if (OidIsValid(fc_finalfn_oid))
		{
			fc_aclresult = pg_proc_aclcheck(fc_finalfn_oid, fc_aggOwner,
										 ACL_EXECUTE);
			if (fc_aclresult != ACLCHECK_OK)
				aclcheck_error(fc_aclresult, OBJECT_FUNCTION,
							   get_func_name(fc_finalfn_oid));
			InvokeFunctionExecuteHook(fc_finalfn_oid);
		}
	}

	/*
	 * 如果选定的 finalfn 不是只读的，我们无法将此聚合作为
	 * 窗口函数运行。这是一个面向用户的错误，因此我们在错误消息上
	 * 比本函数中的其他地方更加小心。
	 */
	if (fc_finalmodify != AGGMODIFY_READ_ONLY)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("aggregate function %s does not support use as a window function",
						format_procedure(fc_wfunc->winfnoid))));

	/* 检测要传递给 finalfn 的参数数量 */
	if (fc_finalextra)
		fc_peraggstate->numFinalArgs = fc_numArguments + 1;
	else
		fc_peraggstate->numFinalArgs = 1;

	/* 解析过渡状态的实际类型，如果是多态的 */
	fc_aggtranstype = resolve_aggregate_transtype(fc_wfunc->winfnoid,
											   fc_aggtranstype,
											   fc_inputTypes,
											   fc_numArguments);

	/* 使用实际的参数和结果类型构建表达式树 */
	build_aggregate_transfn_expr(fc_inputTypes,
								 fc_numArguments,
								 0, /* 还没有有序集合窗口函数 */
								 false, /* 还没有可变参数窗口函数 */
								 fc_aggtranstype,
								 fc_wfunc->inputcollid,
								 fc_transfn_oid,
								 fc_invtransfn_oid,
								 &fc_transfnexpr,
								 &fc_invtransfnexpr);

	/* 设置调用 transfn(s) 和 finalfn 的基础设施 */
	fmgr_info(fc_transfn_oid, &fc_peraggstate->transfn);
	fmgr_info_set_expr((Node *) fc_transfnexpr, &fc_peraggstate->transfn);

	if (OidIsValid(fc_invtransfn_oid))
	{
		fmgr_info(fc_invtransfn_oid, &fc_peraggstate->invtransfn);
		fmgr_info_set_expr((Node *) fc_invtransfnexpr, &fc_peraggstate->invtransfn);
	}

	if (OidIsValid(fc_finalfn_oid))
	{
		build_aggregate_finalfn_expr(fc_inputTypes,
									 fc_peraggstate->numFinalArgs,
									 fc_aggtranstype,
									 fc_wfunc->wintype,
									 fc_wfunc->inputcollid,
									 fc_finalfn_oid,
									 &fc_finalfnexpr);
		fmgr_info(fc_finalfn_oid, &fc_peraggstate->finalfn);
		fmgr_info_set_expr((Node *) fc_finalfnexpr, &fc_peraggstate->finalfn);
	}

	/* 获取相关数据类型的信息 */
	get_typlenbyval(fc_wfunc->wintype,
					&fc_peraggstate->resulttypeLen,
					&fc_peraggstate->resulttypeByVal);
	get_typlenbyval(fc_aggtranstype,
					&fc_peraggstate->transtypeLen,
					&fc_peraggstate->transtypeByVal);

	/*
	 * initval 可能为 null，因此不要尝试将其作为结构字段访问。
	 * 必须通过 SysCacheGetAttr 以困难的方式进行。
	 */
	fc_textInitVal = SysCacheGetAttr(AGGFNOID, fc_aggTuple, fc_initvalAttNo,
								  &fc_peraggstate->initValueIsNull);

	if (fc_peraggstate->initValueIsNull)
		fc_peraggstate->initValue = (Datum) 0;
	else
		fc_peraggstate->initValue = fc_GetAggInitVal(fc_textInitVal,
											   fc_aggtranstype);

	/*
	 * 如果 transfn 是严格的，并且 initval 为 NULL，确保输入类型
	 * 和 transtype 是相同的（或至少是二进制兼容的），以便
	 * 可以使用第一个输入值作为初始 transValue。这
	 * 应在聚合定义时检查，但我们必须再次检查以防 transfn 的严格性属性已改变。
	 */
	if (fc_peraggstate->transfn.fn_strict && fc_peraggstate->initValueIsNull)
	{
		if (fc_numArguments < 1 ||
			!IsBinaryCoercible(fc_inputTypes[0], fc_aggtranstype))
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
					 errmsg("aggregate %u needs to have compatible input type and transition type",
							fc_wfunc->winfnoid)));
	}

	/*
	 * 坚持前向和逆向过渡函数具有相同的严格性设置。
	 * 允许它们有所不同将需要在 advance_windowaggregate 和
	 * advance_windowaggregate_base 中处理更多特殊情况，但没有明显的好处。
	 * 这应在聚合定义时检查，但我们必须再次检查以防任一函数的严格性属性已改变。
	 */
	if (OidIsValid(fc_invtransfn_oid) &&
		fc_peraggstate->transfn.fn_strict != fc_peraggstate->invtransfn.fn_strict)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
				 errmsg("strictness of aggregate's forward and inverse transition functions must match")));

	/*
	 * 移动聚合使用它们自己的 aggcontext。
	 *
	 * 这是必要的，因为它们可能在不同的时间重新开始，因此我们
	 * 可能无法重置共享上下文。我们无法让聚合负责清理自己的工作，
	 * 因为严格聚合必须在移除最后一个非 NULL 输入时重新启动，
	 * 而聚合不会意识到这种情况的发生。此外，仅仅在重新启动时
	 * 释放 transValue 不会有所帮助，因为我们会错过任何间接引用的数据。
	 * 理论上，我们可以使移动聚合的内存分配规则与以往普通聚合的不同，
	 * 但这似乎不太好且可能导致内存泄漏。
	 */
	if (OidIsValid(fc_invtransfn_oid))
		fc_peraggstate->aggcontext =
			AllocSetContextCreate(CurrentMemoryContext,
								  "WindowAgg Per Aggregate",
								  ALLOCSET_DEFAULT_SIZES);
	else
		fc_peraggstate->aggcontext = fc_winstate->aggcontext;

	ReleaseSysCache(fc_aggTuple);

	return fc_peraggstate;
}

static Datum fc_GetAggInitVal(Datum fc_textInitVal, Oid fc_transtype)
{
	Oid			fc_typinput,
				fc_typioparam;
	char	   *fc_strInitVal;
	Datum		fc_initVal;

	getTypeInputInfo(fc_transtype, &fc_typinput, &fc_typioparam);
	fc_strInitVal = TextDatumGetCString(fc_textInitVal);
	fc_initVal = OidInputFunctionCall(fc_typinput, fc_strInitVal,
								   fc_typioparam, -1);
	pfree(fc_strInitVal);
	return fc_initVal;
}

/*
 * are_peers
 * 比较两行以查看它们是否根据 ORDER BY 子句相等
 *
 * 注意：这不考虑窗口帧模式。
 */
static bool fc_are_peers(WindowAggState *fc_winstate, TupleTableSlot *fc_slot1,
		  TupleTableSlot *fc_slot2)
{
	WindowAgg  *fc_node = (WindowAgg *) fc_winstate->ss.ps.plan;
	ExprContext *fc_econtext = fc_winstate->tmpcontext;

	/* 如果没有 ORDER BY，所有行彼此均为同级 */
	if (fc_node->ordNumCols == 0)
		return true;

	fc_econtext->ecxt_outertuple = fc_slot1;
	fc_econtext->ecxt_innertuple = fc_slot2;
	return ExecQualAndReset(fc_winstate->ordEqfunction, fc_econtext);
}


/*
 * window_gettupleslot
 * 获取当前分区的第 pos 行元组到槽中，
 * 使用 winobj 的读取指针
 *
 * 如果成功则返回 true，若没有这样的行则返回 false
 */
static bool fc_window_gettupleslot(WindowObject fc_winobj, int64 fc_pos, TupleTableSlot *fc_slot)
{
	WindowAggState *fc_winstate = fc_winobj->winstate;
	MemoryContext fc_oldcontext;

	/* 通常会在一行中重复调用 */
	CHECK_FOR_INTERRUPTS();

	/****** 不允许将 -1 传递给 spool_tuples *******/
	if (fc_pos < 0)
		return false;

	/* 如果需要，将元组获取到临时存储中 */
	fc_spool_tuples(fc_winstate, fc_pos);

	if (fc_pos >= fc_winstate->spooled_rows)
		return false;

	if (fc_pos < fc_winobj->markpos)
		elog(ERROR, "cannot fetch row before WindowObject's mark position");

	fc_oldcontext = MemoryContextSwitchTo(fc_winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);

	tuplestore_select_read_pointer(fc_winstate->buffer, fc_winobj->readptr);

	/*
	 * 向前或向后移动，直到我们接近想要的元组。
	 */
	if (fc_winobj->seekpos < fc_pos - 1)
	{
		if (!tuplestore_skiptuples(fc_winstate->buffer,
								   fc_pos - 1 - fc_winobj->seekpos,
								   true))
			elog(ERROR, "unexpected end of tuplestore");
		fc_winobj->seekpos = fc_pos - 1;
	}
	else if (fc_winobj->seekpos > fc_pos + 1)
	{
		if (!tuplestore_skiptuples(fc_winstate->buffer,
								   fc_winobj->seekpos - (fc_pos + 1),
								   false))
			elog(ERROR, "unexpected end of tuplestore");
		fc_winobj->seekpos = fc_pos + 1;
	}
	else if (fc_winobj->seekpos == fc_pos)
	{
		/*
		 * 当前位置没有 API 可以重新获取元组。我们
		 * 必须向前移动一个元组，然后向后移动一个元组。
		 * （我们不这样做是因为我们可能试图在标记之前
		 * 获取行，这是不允许的。）XXX 这个情况可以进行
		 * 优化。
		 */
		tuplestore_advance(fc_winstate->buffer, true);
		fc_winobj->seekpos++;
	}

	/*
	 * 现在我们应该在想要的元组之前或之后的元组上，
	 * 所以根据需要向前或向后获取。
	 */
	if (fc_winobj->seekpos > fc_pos)
	{
		if (!tuplestore_gettupleslot(fc_winstate->buffer, false, true, fc_slot))
			elog(ERROR, "unexpected end of tuplestore");
		fc_winobj->seekpos--;
	}
	else
	{
		if (!tuplestore_gettupleslot(fc_winstate->buffer, true, true, fc_slot))
			elog(ERROR, "unexpected end of tuplestore");
		fc_winobj->seekpos++;
	}

	Assert(fc_winobj->seekpos == fc_pos);

	MemoryContextSwitchTo(fc_oldcontext);

	return true;
}


/***********************************************************************
 * 提供给窗口函数的 API
 ***********************************************************************/


/*
 * WinGetPartitionLocalMemory
 *		获取在分区处理结束之前生效的工作内存
 *
 * 在特定分区的第一次调用时，这会分配并清零请求的
 * 空间。后续调用仅返回相同的内存块。
 *
 * 通过这种方式获得的内存通常用于存放应该
 * 在每个新分区自动重置的状态。如果窗口函数希望跨整个
 * 查询保持状态，可以使用 fcinfo->fn_extra
 * 以通常的方式进行处理。
 */
void * WinGetPartitionLocalMemory(WindowObject fc_winobj, Size fc_sz)
{
	Assert(WindowObjectIsValid(fc_winobj));
	if (fc_winobj->localmem == NULL)
		fc_winobj->localmem =
			MemoryContextAllocZero(fc_winobj->winstate->partcontext, fc_sz);
	return fc_winobj->localmem;
}

/*
 * WinGetCurrentPosition
 *		返回当前分区内当前行的位置（从 0 开始计数）。
 */
int64 WinGetCurrentPosition(WindowObject fc_winobj)
{
	Assert(WindowObjectIsValid(fc_winobj));
	return fc_winobj->winstate->currentpos;
}

/*
 * WinGetPartitionRowCount
 *		返回当前分区包含的行总数。
 *
 * 注意：这是一项相对昂贵的操作，因为它迫使整个
 * 分区一次性“传输”到元组存储中。然而，一旦执行，
 * 在同一分区内的后续调用是便宜的。
 */
int64 WinGetPartitionRowCount(WindowObject fc_winobj)
{
	Assert(WindowObjectIsValid(fc_winobj));
	fc_spool_tuples(fc_winobj->winstate, -1);
	return fc_winobj->winstate->spooled_rows;
}

/*
 * WinSetMarkPosition
 *		为窗口对象设置“标记”位置，这是允许在当前
 *		分区的所有后续操作中获取的最旧行号（从 0
 *		开始计数）。
 *
 * 窗口函数不必调用此函数，但鼓励在可能的情况下
 * 向前移动标记，以减少元组存储的大小并防止将
 * 行溢出到磁盘。
 */
void WinSetMarkPosition(WindowObject fc_winobj, int64 fc_markpos)
{
	WindowAggState *fc_winstate;

	Assert(WindowObjectIsValid(fc_winobj));
	fc_winstate = fc_winobj->winstate;

	if (fc_markpos < fc_winobj->markpos)
		elog(ERROR, "cannot move WindowObject's mark position backward");
	tuplestore_select_read_pointer(fc_winstate->buffer, fc_winobj->markptr);
	if (fc_markpos > fc_winobj->markpos)
	{
		tuplestore_skiptuples(fc_winstate->buffer,
							  fc_markpos - fc_winobj->markpos,
							  true);
		fc_winobj->markpos = fc_markpos;
	}
	tuplestore_select_read_pointer(fc_winstate->buffer, fc_winobj->readptr);
	if (fc_markpos > fc_winobj->seekpos)
	{
		tuplestore_skiptuples(fc_winstate->buffer,
							  fc_markpos - fc_winobj->seekpos,
							  true);
		fc_winobj->seekpos = fc_markpos;
	}
}

/*
 * WinRowsArePeers
 *		比较两个行（通过在分区中的绝对位置指定）以查看
 *		它们是否根据 ORDER BY 子句相等。
 *
 * 注意：这不考虑窗口帧模式。
 */
bool WinRowsArePeers(WindowObject fc_winobj, int64 fc_pos1, int64 fc_pos2)
{
	WindowAggState *fc_winstate;
	WindowAgg  *fc_node;
	TupleTableSlot *fc_slot1;
	TupleTableSlot *fc_slot2;
	bool		fc_res;

	Assert(WindowObjectIsValid(fc_winobj));
	fc_winstate = fc_winobj->winstate;
	fc_node = (WindowAgg *) fc_winstate->ss.ps.plan;

	/* 如果没有 ORDER BY，所有行都是同级；无需获取它们 */
	if (fc_node->ordNumCols == 0)
		return true;

	/*
	 * 注意：在这里使用 temp_slot_2 是可以的，因为我们没有
	 * 调用任何与帧相关的函数（那些函数往往会搞乱 temp_slot_2）。
	 */
	fc_slot1 = fc_winstate->temp_slot_1;
	fc_slot2 = fc_winstate->temp_slot_2;

	if (!fc_window_gettupleslot(fc_winobj, fc_pos1, fc_slot1))
		elog(ERROR, "specified position is out of window: " INT64_FORMAT,
			 fc_pos1);
	if (!fc_window_gettupleslot(fc_winobj, fc_pos2, fc_slot2))
		elog(ERROR, "specified position is out of window: " INT64_FORMAT,
			 fc_pos2);

	fc_res = fc_are_peers(fc_winstate, fc_slot1, fc_slot2);

	ExecClearTuple(fc_slot1);
	ExecClearTuple(fc_slot2);

	return fc_res;
}

/*
 * WinGetFuncArgInPartition
 *		在指定的分区行上评估窗口函数的参数表达式。
 *		该行以 lseek(2) 风格标识，即相对于当前、
 *		第一或最后一行。
 *
 * argno: 要评估的参数编号（从 0 开始计数）
 * relpos: 从寻址位置的带符号行计数偏移
 * seektype: WINDOW_SEEK_CURRENT、WINDOW_SEEK_HEAD 或 WINDOW_SEEK_TAIL
 * set_mark: 如果找到该行且 set_mark 为真，则标记
 *		将作为副作用移动到该行。
 * isnull: 输出参数，接收结果的 isnull 状态
 * isout: 输出参数，设置为指示目标行位置
 *		是否超出分区（如果调用者不关心可以传递 NULL）
 *
 * 指定一个不存在的行并不是错误，只会导致 null 结果
 * （如果 isout 不为 NULL，还会将 *isout 设置为 true）。
 */
Datum WinGetFuncArgInPartition(WindowObject fc_winobj, int fc_argno,
						 int fc_relpos, int fc_seektype, bool set_mark,
						 bool *fc_isnull, bool *fc_isout)
{
	WindowAggState *fc_winstate;
	ExprContext *fc_econtext;
	TupleTableSlot *fc_slot;
	bool		fc_gottuple;
	int64		fc_abs_pos;

	Assert(WindowObjectIsValid(fc_winobj));
	fc_winstate = fc_winobj->winstate;
	fc_econtext = fc_winstate->ss.ps.ps_ExprContext;
	fc_slot = fc_winstate->temp_slot_1;

	switch (fc_seektype)
	{
		case WINDOW_SEEK_CURRENT:
			fc_abs_pos = fc_winstate->currentpos + fc_relpos;
			break;
		case WINDOW_SEEK_HEAD:
			fc_abs_pos = fc_relpos;
			break;
		case WINDOW_SEEK_TAIL:
			fc_spool_tuples(fc_winstate, -1);
			fc_abs_pos = fc_winstate->spooled_rows - 1 + fc_relpos;
			break;
		default:
			elog(ERROR, "unrecognized window seek type: %d", fc_seektype);
			fc_abs_pos = 0;		/* 保持编译器安静 */
			break;
	}

	fc_gottuple = fc_window_gettupleslot(fc_winobj, fc_abs_pos, fc_slot);

	if (!fc_gottuple)
	{
		if (fc_isout)
			*fc_isout = true;
		*fc_isnull = true;
		return (Datum) 0;
	}
	else
	{
		if (fc_isout)
			*fc_isout = false;
		if (set_mark)
			WinSetMarkPosition(fc_winobj, fc_abs_pos);
		fc_econtext->ecxt_outertuple = fc_slot;
		return ExecEvalExpr((ExprState *) list_nth(fc_winobj->argstates, fc_argno),
							fc_econtext, fc_isnull);
	}
}


/*
 * WinGetFuncArgInFrame
 *		在指定窗口帧的某一行上评估窗口函数的参数表达式。 行的标识方式类似于 lseek(2)，即相对于帧的第一行或最后一行。 （我们不支持此处的 WINDOW_SEEK_CURRENT，因为如果当前行不在帧内，那么它意味着什么并不十分明确。）
 *
 * argno: 要评估的参数编号（从 0 开始计数）
 * relpos: 从搜索位置起的带符号行计数偏移
 * seektype: WINDOW_SEEK_HEAD 或 WINDOW_SEEK_TAIL
 * set_mark: 如果行被找到/在帧内并且 set_mark 为真，则标记会作为副作用移动到该行。
 * isnull: 输出参数，接收结果的 isnull 状态
 * isout: 输出参数，设置以指示目标行位置是否超出帧（如果调用者不关心可以传递 NULL）
 *
 * 指定一个不存在或不在帧内的行不是错误，这只是导致 null 结果（如果 isout 不为 NULL，则还会将 *isout 设置为 true）。
 *
 * 请注意，一些排除子句选项可能导致在分区中在帧内的行不连续。 但是在测量 relpos 时，我们仅计算在帧内的行。
 *
 * set_mark 标志被解释为调用者将在连续调用中指定一个常量（或可能是单调递增的）relpos，因此 *如果没有排除子句*，则无需在先前获取的行之前获取行。 但我们并不指望调用者知道如何处理排除子句。 因此，如果存在排除子句，我们将负责将标记请求调整为在上述关于 relpos 的假设下安全的内容。
 */
Datum WinGetFuncArgInFrame(WindowObject fc_winobj, int fc_argno,
					 int fc_relpos, int fc_seektype, bool set_mark,
					 bool *fc_isnull, bool *fc_isout)
{
	WindowAggState *fc_winstate;
	ExprContext *fc_econtext;
	TupleTableSlot *fc_slot;
	int64		fc_abs_pos;
	int64		fc_mark_pos;

	Assert(WindowObjectIsValid(fc_winobj));
	fc_winstate = fc_winobj->winstate;
	fc_econtext = fc_winstate->ss.ps.ps_ExprContext;
	fc_slot = fc_winstate->temp_slot_1;

	switch (fc_seektype)
	{
		case WINDOW_SEEK_CURRENT:
			elog(ERROR, "WINDOW_SEEK_CURRENT is not supported for WinGetFuncArgInFrame");
			fc_abs_pos = fc_mark_pos = 0; /* 保持编译器安静 */
			break;
		case WINDOW_SEEK_HEAD:
			/* 拒绝 relpos < 0 很简单，简化了下面的代码 */
			if (fc_relpos < 0)
				goto out_of_frame;
			update_frameheadpos(fc_winstate);
			fc_abs_pos = fc_winstate->frameheadpos + fc_relpos;
			fc_mark_pos = fc_abs_pos;

			/*
			 * 如果启用了排除选项，则考虑排除选项，但仅推进 abs_pos 而不是 mark_pos。 这防止了当前行的peer组的变化导致试图在某些先前的标记位置之前获取一行。
			 *
			 * 请注意，在某些极端情况下，例如当前行处于帧外， 这些计算在理论上过于简单，但这无关紧要，因为我们最终将决定该行超出帧。 我们不尝试避免获取超出帧末尾的行；无论如何在某些情况下都会发生这种情况。
			 */
			switch (fc_winstate->frameOptions & FRAMEOPTION_EXCLUSION)
			{
				case 0:
					/* 不需要调整 */
					break;
				case FRAMEOPTION_EXCLUDE_CURRENT_ROW:
					if (fc_abs_pos >= fc_winstate->currentpos &&
						fc_winstate->currentpos >= fc_winstate->frameheadpos)
						fc_abs_pos++;
					break;
				case FRAMEOPTION_EXCLUDE_GROUP:
					update_grouptailpos(fc_winstate);
					if (fc_abs_pos >= fc_winstate->groupheadpos &&
						fc_winstate->grouptailpos > fc_winstate->frameheadpos)
					{
						int64		fc_overlapstart = Max(fc_winstate->groupheadpos,
													   fc_winstate->frameheadpos);

						fc_abs_pos += fc_winstate->grouptailpos - fc_overlapstart;
					}
					break;
				case FRAMEOPTION_EXCLUDE_TIES:
					update_grouptailpos(fc_winstate);
					if (fc_abs_pos >= fc_winstate->groupheadpos &&
						fc_winstate->grouptailpos > fc_winstate->frameheadpos)
					{
						int64		fc_overlapstart = Max(fc_winstate->groupheadpos,
													   fc_winstate->frameheadpos);

						if (fc_abs_pos == fc_overlapstart)
							fc_abs_pos = fc_winstate->currentpos;
						else
							fc_abs_pos += fc_winstate->grouptailpos - fc_overlapstart - 1;
					}
					break;
				default:
					elog(ERROR, "unrecognized frame option state: 0x%x",
						 fc_winstate->frameOptions);
					break;
			}
			break;
		case WINDOW_SEEK_TAIL:
			/* 拒绝 relpos > 0 很简单，简化了下面的代码 */
			if (fc_relpos > 0)
				goto out_of_frame;
			update_frametailpos(fc_winstate);
			fc_abs_pos = fc_winstate->frametailpos - 1 + fc_relpos;

			/*
			 * 如果启用了排除选项，则考虑排除选项。 如果没有排除，我们可以安全地将标记设置在访问的行上。 但是如果有排除，我们只能标记帧的开始，因为我们不能确定排除可能导致我们在将来获取的帧中的位置有多远。 此外，我们必须在这里实际检查 frameheadpos，因为如果标记可能已经在那里，如果尝试在帧开始之前获取一行则是不安全的。
			 */
			switch (fc_winstate->frameOptions & FRAMEOPTION_EXCLUSION)
			{
				case 0:
					/* 不需要调整 */
					fc_mark_pos = fc_abs_pos;
					break;
				case FRAMEOPTION_EXCLUDE_CURRENT_ROW:
					if (fc_abs_pos <= fc_winstate->currentpos &&
						fc_winstate->currentpos < fc_winstate->frametailpos)
						fc_abs_pos--;
					update_frameheadpos(fc_winstate);
					if (fc_abs_pos < fc_winstate->frameheadpos)
						goto out_of_frame;
					fc_mark_pos = fc_winstate->frameheadpos;
					break;
				case FRAMEOPTION_EXCLUDE_GROUP:
					update_grouptailpos(fc_winstate);
					if (fc_abs_pos < fc_winstate->grouptailpos &&
						fc_winstate->groupheadpos < fc_winstate->frametailpos)
					{
						int64		fc_overlapend = Min(fc_winstate->grouptailpos,
													 fc_winstate->frametailpos);

						fc_abs_pos -= fc_overlapend - fc_winstate->groupheadpos;
					}
					update_frameheadpos(fc_winstate);
					if (fc_abs_pos < fc_winstate->frameheadpos)
						goto out_of_frame;
					fc_mark_pos = fc_winstate->frameheadpos;
					break;
				case FRAMEOPTION_EXCLUDE_TIES:
					update_grouptailpos(fc_winstate);
					if (fc_abs_pos < fc_winstate->grouptailpos &&
						fc_winstate->groupheadpos < fc_winstate->frametailpos)
					{
						int64		fc_overlapend = Min(fc_winstate->grouptailpos,
													 fc_winstate->frametailpos);

						if (fc_abs_pos == fc_overlapend - 1)
							fc_abs_pos = fc_winstate->currentpos;
						else
							fc_abs_pos -= fc_overlapend - 1 - fc_winstate->groupheadpos;
					}
					update_frameheadpos(fc_winstate);
					if (fc_abs_pos < fc_winstate->frameheadpos)
						goto out_of_frame;
					fc_mark_pos = fc_winstate->frameheadpos;
					break;
				default:
					elog(ERROR, "unrecognized frame option state: 0x%x",
						 fc_winstate->frameOptions);
					fc_mark_pos = 0;	/* 保持编译器安静 */
					break;
			}
			break;
		default:
			elog(ERROR, "unrecognized window seek type: %d", fc_seektype);
			fc_abs_pos = fc_mark_pos = 0; /* 保持编译器安静 */
			break;
	}

	if (!fc_window_gettupleslot(fc_winobj, fc_abs_pos, fc_slot))
		goto out_of_frame;

	/* 上面的代码并未检测到所有超出帧的情况，因此进行检查 */
	if (fc_row_is_in_frame(fc_winstate, fc_abs_pos, fc_slot) <= 0)
		goto out_of_frame;

	if (fc_isout)
		*fc_isout = false;
	if (set_mark)
		WinSetMarkPosition(fc_winobj, fc_mark_pos);
	fc_econtext->ecxt_outertuple = fc_slot;
	return ExecEvalExpr((ExprState *) list_nth(fc_winobj->argstates, fc_argno),
						fc_econtext, fc_isnull);

out_of_frame:
	if (fc_isout)
		*fc_isout = true;
	*fc_isnull = true;
	return (Datum) 0;
}

/*
 * WinGetFuncArgCurrent
 *		在当前行上评估窗口函数的参数表达式。
 *
 * argno: 要评估的参数编号（从 0 开始计数）
 * isnull: 输出参数，接收结果的 isnull 状态
 *
 * 注意：这并不完全等同于 WinGetFuncArgInPartition 或定位当前行的 WinGetFuncArgInFrame，因为即使 WindowObject 的标记已设置在当前行之外，它也会成功。 这通常应适用于窗口函数的“普通”参数，例如 lead() 或 lag() 的偏移参数。
 */
Datum WinGetFuncArgCurrent(WindowObject fc_winobj, int fc_argno, bool *fc_isnull)
{
	WindowAggState *fc_winstate;
	ExprContext *fc_econtext;

	Assert(WindowObjectIsValid(fc_winobj));
	fc_winstate = fc_winobj->winstate;

	fc_econtext = fc_winstate->ss.ps.ps_ExprContext;

	fc_econtext->ecxt_outertuple = fc_winstate->ss.ss_ScanTupleSlot;
	return ExecEvalExpr((ExprState *) list_nth(fc_winobj->argstates, fc_argno),
						fc_econtext, fc_isnull);
}
